How to Tunnel Securely with Caddy and Basic Authentication

When you expose a local service with asd expose, it gets a public
HTTPS URL. By default, anyone with that URL can access your service. ASD's
built-in Caddy integration lets you add HTTP Basic Authentication with a few
lines in asd.yaml — no manual Caddy configuration, no bcrypt hashing,
no separate reverse proxy setup.
How ASD handles Caddy
ASD manages a local Caddy reverse proxy automatically. When you define
services in asd.yaml, ASD generates Caddy routes, handles
routing, and applies authentication. You never touch a Caddyfile directly.
Traffic flows like this:
Internet → ASD Cloud → SSH Tunnel → Caddy (local) → Your Service
↑
Security layer
Prerequisites
- ASD CLI installed (
asd initto set up your workspace) - Authenticated with ASD (
asd login) - A local service running (we use port 3000 in this guide)
Step 1: Configure your service with basic auth
Add your service to asd.yaml with a basic_auth
block. You can enable it globally or per-service:
version: 1
project:
name: my-project
domain: localhost
network:
caddy:
enable: true
basic_auth:
enabled: true
realm: "My Project"
services:
my-app:
dial: "127.0.0.1:3000"
host: "myapp.localhost"
The basic_auth block under caddy protects all
services. ASD reads credentials from your .env file:
ASD_BASIC_AUTH_USERNAME=admin
ASD_BASIC_AUTH_PASSWORD=your-secure-password
These are set automatically when you run asd init. The password
is auto-generated with random characters by default.
Step 2: Apply the network configuration
Apply the configuration and start Caddy:
asd net apply --caddy
This generates Caddy routes with the authentication handler, bcrypt-hashes the password automatically, and applies everything to the running Caddy instance.
Step 3: Verify with the network TUI
Open the ASD network dashboard to verify your service is running and protected:
asd net
You should see your service with a green checkmark. Use these keyboard shortcuts:
| Key | Action |
|---|---|
Tab | Cycle tabs (Services, Projects, Logs) |
Enter | Actions menu for selected service |
Ctrl+R | Refresh health status |
Ctrl+Q | Quit |
Step 4: Expose via tunnel
Expose the service through an ASD tunnel. Because Caddy sits in front of your service, the tunnel endpoint is already protected:
asd expose 3000 myapp
The second argument is the subdomain prefix for your tunnel URL.
Output:
Local: http://localhost:3000
Caddy: http://myapp.localhost
Tunnel: https://myapp-abc123.cicd.eu1.asd.engineer
Anyone accessing the tunnel URL will see a Basic Auth prompt before reaching your service.
Per-service credentials with macros
Each service can have its own credentials, auto-generated via macros. First,
define the variables in your tpl.env template:
# tpl.env — macros expanded during asd init / asd env-init
ASD_BASIC_AUTH_USERNAME=admin
ASD_BASIC_AUTH_PASSWORD=getRandomString(length=32,charset=safe)
# Custom credentials for admin panel
ADMIN_PANEL_USERNAME=admin
ADMIN_PANEL_PASSWORD=getRandomString(length=32,charset=safe)
After asd init, your .env has real values:
# .env (generated)
ASD_BASIC_AUTH_USERNAME=admin
ASD_BASIC_AUTH_PASSWORD=k7mx9pqr2st8vw1yb4cf6dh3jn5q8z2a
ADMIN_PANEL_USERNAME=admin
ADMIN_PANEL_PASSWORD=p3rk8tn2wq5xj7mv9cf4yd6ah1bs0e3g
Then reference those credentials in asd.yaml per service. This lets
you mix public and protected services, each with separate credentials:
network:
caddy:
enable: true
basic_auth:
enabled: true
realm: "My Project"
services:
public-api:
dial: "127.0.0.1:3000"
host: "api.localhost"
basic_auth:
enabled: false # No auth for this service
admin-panel:
dial: "127.0.0.1:8080"
host: "admin.localhost"
basic_auth:
enabled: true
realm: "Admin Only"
username: "${ADMIN_PANEL_USERNAME}"
password: "${ADMIN_PANEL_PASSWORD}"
The global ASD_BASIC_AUTH_* credentials protect all other
services. The admin panel uses its own separate credentials from
.env. To rotate credentials, run
asd env-init --override --yes and all macros regenerate.
Selective route protection
By default, basic auth applies to all route types. You can restrict it to specific route types:
network:
caddy:
basic_auth:
enabled: true
routes:
- host # Only protect host-based routes
Macros: auto-generated credentials and ports
ASD uses macros in tpl.env templates to auto-generate secure
values during asd init. You never need to generate passwords
manually — ASD does it for you.
# tpl.env (template, expanded during asd init)
ASD_BASIC_AUTH_USERNAME=admin
ASD_BASIC_AUTH_PASSWORD=getRandomString(length=32,charset=safe)
After running asd init, your .env contains a real random
value:
# .env (generated)
ASD_BASIC_AUTH_USERNAME=admin
ASD_BASIC_AUTH_PASSWORD=k7mx9pqr2st8vw1yb4cf6dh3jn5q8z2a
The same macro system handles port allocation to prevent conflicts when running multiple projects or parallel CI:
# tpl.env — ports auto-allocated, no conflicts
ASD_CADDY_PORT_HTTP=getRandomPort(min=30000,max=65000)
ASD_CADDY_PORT_HTTPS=getRandomPort(min=30000,max=65000)
ASD_CADDY_ADMIN_PORT=getRandomPort(min=30000,max=65000)
Available macros:
| Macro | Description |
|---|---|
getRandomString(length,charset) |
Generate a secure random string (charsets: safe, alnum, hex, alpha) |
getRandomPort(min,max) |
Allocate an unused port (OS-level check, no conflicts) |
getRandomPorts(n,sep) |
Allocate multiple ports at once |
getPortRange(size,min,max) |
Reserve a contiguous range of ports |
To regenerate all macro values (e.g., rotate passwords):
asd env-init --override --yes
If you prefer to set your own password manually, generate one with:
openssl rand -base64 24
Then set it in your .env file directly. ASD will use whatever
value is in .env, whether macro-generated or manually set.
Summary
ASD's Caddy integration handles authentication as part of your service
configuration. Define basic_auth in asd.yaml, apply
with asd net apply --caddy, and expose with
asd expose. No manual Caddyfiles, no bcrypt hashing by hand, no
separate reverse proxy setup. The entire flow — from local routing to public
HTTPS with authentication — is managed through the ASD CLI.
Kelvin Wuite
Kelvin Wuite is the founder of Accelerated Software Development B.V. With over eighteen years of development experience, he has witnessed the same patterns repeat across every software team: endless documentation, manual preparation, environment mismatches, and fragmented collaboration. His drive is to remove these barriers, enabling engineers to work together in unified environments with shorter feedback loops and hands-on collaboration. Since 2015 he has been refining these ideas, leading to ASD — a platform designed to create a faster, more integrated way for development teams to collaborate in an age where AI is thriving.
Related Articles
How to Create Your First HTTPS Tunnel in 30 Seconds
Install ASD CLI and expose your local development server to the internet with a public HTTPS URL. One command, 30 seconds, no configuration needed.
TunnelsHow to Expose Local Services with ASD Tunnels
Learn three ways to expose local services with ASD: quick expose for instant sharing, asd.yaml for daily development, and tunnel tokens for CI/CD automation.
SecurityHow to Use ASD Vault for Encrypted Secret Management
Learn how to use ASD Vault for encrypted secret management. Store, retrieve, and inject secrets powered by Supabase Vault and pgsodium encryption.