Site Address #
In the Caddyfile, every site block must begin with one or more site addresses known as the Site Address. This first element acts as a gatekeeper — it defines for which request traffic the configuration block applies.
Writing the site address correctly isn’t just a matter of aesthetics or ease of writing. In Caddy, the site address format directly affects core server behavior: whether Caddy enables Automatic HTTPS, uses a public or internal certificate authority (CA), listens on specific ports, or even configures automatic traffic redirection. A small format mistake here can prevent the server from obtaining an SSL certificate, or wrongly route users’ requests to another site.
Automatic HTTPS Decision Matrix #
Caddy is famous for its Automatic HTTPS feature, enabled by default. However, not every site address you write in the Caddyfile triggers this feature.
To help you understand how Caddy determines the TLS status for each address, look at the decision tree below:
flowchart TD
Address{"Start: Evaluate Site Address"} --> IsIP{"Is it an IP Address?"}
IsIP -- "Yes" --> NoAutoHTTPS["Automatic HTTPS: NO\n(HTTP Only / Manual TLS Needed)"]
IsIP -- "No" --> IsLocalhost{"Is it 'localhost' or *.localhost?"}
IsLocalhost -- "Yes" --> InternalCA["Automatic HTTPS: YES\n(Using Caddy's Internal CA)"]
IsLocalhost -- "No" --> HasPort{"Is a port specified explicitly?"}
HasPort -- "Yes" --> WhichPort{"Which port?"}
WhichPort -- "80" --> NoAutoHTTPS
WhichPort -- "Other (443, 8443, etc.)" --> AutoHTTPS["Automatic HTTPS: YES\n(Let's Encrypt / ZeroSSL)"]
HasPort -- "No" --> HasScheme{"Is http:// explicitly used?"}
HasScheme -- "Yes" --> NoAutoHTTPS
HasScheme -- "No" --> IsWildcard{"Is it a Wildcard (*.domain.com)?"}
IsWildcard -- "Yes" --> AutoHTTPSWildcard["Automatic HTTPS: YES\n(Let's Encrypt / ZeroSSL)\n*Requires DNS-01 Challenge"]
IsWildcard -- "No" --> AutoHTTPSTo complement the diagram above, here’s a quick reference table of site address formats with their automatic HTTPS status:
| Site Address Format | Automatic HTTPS? | CA Type | Description |
|---|---|---|---|
example.com | YES | Public | Standard domain. Listens on port 80 (redirect) & 443 (TLS). |
example.com:443 | YES | Public | Explicit port 443. Behaves the same as without a port. |
example.com:8443 | YES | Public | Non-standard HTTPS port. Automatic certificates active on that port. |
http://example.com | NO | - | The http:// scheme forcibly disables TLS. Listens only on port 80. |
example.com:80 | NO | - | Specifying port 80 turns off automatic HTTPS. HTTP only. |
localhost | YES | Internal | Uses Caddy’s local CA. Browsers need the Caddy root CA installed. |
app.localhost | YES | Internal | localhost subdomains also use the internal CA automatically. |
192.168.1.100 | NO | - | Local/public IP address. Cannot be validated by public ACME without a domain name. |
127.0.0.1 | YES | Internal | Special loopback IP. Caddy issues a self-signed cert via the internal CA. |
*.example.com | YES | Public | Wildcard domain. Requires DNS-01 Challenge authentication for validation. |
:80 | NO | - | Listens only on port 80 for all hosts (HTTP catch-all). |
:443 | YES | Internal/Public | Listens on port 443 for all hosts. Needs on-demand TLS or default certs. |
Basic Site Address Formats #
Let’s review each site address format above in detail along with its server implications:
1. Domain Name Only (Most Common Format) #
# Caddy automatically enables HTTPS for this domain
# Caddy listens on port 80 (for HTTPS redirect) and 443 (actual HTTPS)
example.com {
file_server
}
This is the golden standard format in Caddy. Caddy handles all SSL/TLS matters in the background without any extra instructions from you.
2. Domain with an Explicit Port #
# Enable HTTPS on the custom port 8443
example.com:8443 {
file_server
}
This scenario is useful if your server sits behind a firewall that restricts the standard 443 port, or if you run several web server services on the same machine with different TLS ports.
3. IP Address #
# Caddy will not enable automatic HTTPS
# The server is only accessible via HTTP on port 80 (default for non-TLS site blocks)
192.168.1.50 {
file_server
}
By default, public certificate authorities like Let’s Encrypt don’t issue SSL certificates for private IP addresses (RFC 1918) like 192.168.x.x or 10.x.x.x. Therefore, Caddy disables automatic HTTPS on IP addresses to prevent perpetual domain validation failure errors.
4. The Special Localhost Address #
# Caddy enables HTTPS using the internal CA (Local CA)
# Perfect for secure local (development) environments
localhost {
reverse_proxy localhost:3000
}
When you use localhost or domains ending in .localhost (for example, myproject.localhost), Caddy acts as its own local Certificate Authority. Caddy creates a root certificate, adds it to your local OS trust store (requiring admin password authorization on first run), and issues valid SSL certificates for local development.
Wildcard Domains #
Wildcard domains are used when you want to serve every possible subdomain with a single site block (for example, user1.example.com, user2.example.com, etc., all routed to the same block). The format uses an asterisk (*) character at the third-level subdomain position:
# Handle all one-level subdomains under example.com
*.example.com {
# IMPORTANT: Wildcard domains require the DNS-01 Challenge
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
reverse_proxy localhost:8000
}
Why Does It Have to Be a DNS Challenge? #
By default, Caddy uses the HTTP-01 challenge to validate domain ownership. In the HTTP-01 challenge, Let’s Encrypt sends a request to a special verification file on port 80 of that domain. However, Let’s Encrypt cannot validate wildcard domains (*.example.com) using HTTP-01 because it’s impossible to create verification files on all dynamic subdomains in real time.
Therefore, if you define a wildcard site address, you must configure the TLS DNS Challenge module. Caddy creates a temporary TXT record in your DNS provider (like Cloudflare, Route 53, or DigitalOcean) to prove domain ownership.
Note also that the *.example.com wildcard only matches one-level subdomains. It won’t match the apex domain (example.com) or multi-level subdomains like app.staging.example.com. If you want to handle both the apex domain and the wildcard, you must write both in the site address:
# Handle the apex domain and subdomains together
example.com
*.example.com {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
reverse_proxy localhost:8000
}
On-Demand TLS (Multi-Tenant) #
If you’re building a SaaS platform where customers can point their own custom domains (for example, app.customer1.com, www.customer2.net) to your server, you can’t write all those domains manually in the Caddyfile.
Caddy provides a remarkable feature called On-Demand TLS. With it, Caddy dynamically requests SSL certificates from Let’s Encrypt at the moment an SSL handshake first happens for a new, unregistered domain.
# 1. Global Options configuration for On-Demand TLS
{
on_demand_tls {
# Your internal API endpoint to check whether a domain is allowed to use your platform
# Caddy sends a GET request with the query parameter '?domain=customer-domain.com'
# Your API must respond with HTTP 200 (allowed) or HTTP 400/403 (denied)
ask http://localhost:5000/api/v1/validate-domain
# Limit new certificate issuance to prevent DDoS attacks
interval 2m
burst 5
}
}
# 2. Catch-all HTTPS site block
:443 {
tls {
# Enable on-demand for this block
on_demand
}
# Forward traffic to your SaaS application server
reverse_proxy localhost:3000
}
[!CAUTION] Never enable
on_demandwithout configuring theaskendpoint! Without theaskendpoint, anyone can point their random domains at your server’s IP, forcing your server to request certificates from Let’s Encrypt. This can burn through your Let’s Encrypt rate limit quota within minutes, fill your disk with junk certificates, or even take your server down from an out-of-memory attack.
Path Prefixes in Site Addresses #
The Caddyfile allows you to include a starting path (path prefix) directly in the site address declaration to create high-level routing separation:
# This block only handles requests starting with /api/
example.com/api/* {
reverse_proxy localhost:8080
}
# This block handles all other requests to example.com
example.com {
root * /var/www/html
file_server
}
Why Are Path Prefixes in Site Addresses Rarely Used? #
Although this feature is available, in modern web architecture practice, writing path prefixes in site addresses is strongly discouraged except for simple needs. The reasons are:
- Less Flexible: This routing is evaluated early at server initialization. You can’t use complex conditional logic or change paths dynamically like with request matchers.
- Confusion Potential: It’s hard to see the whole site routing flow when configuration is split into several different site blocks for the same domain.
- Better Alternative: Using
handleblocks or theroutedirective inside a single site block is far more modular and maintainable.
Binding to a Specific Network Interface #
By default, when you write example.com, Caddy listens on all available network interfaces of the operating system (equivalent to IP 0.0.0.0 for IPv4 and :: for IPv6).
If your server has multiple network cards (for example, a public interface and a private/VPN interface) and you want the site only accessible from the private network, you can bind the site address to that private IP:
# Only listen for example.com requests arriving through the internal VPN IP 10.8.0.1
10.8.0.1:443, example.com {
# Internal/admin configuration
reverse_proxy localhost:9000
}
For plain HTTP scenarios without a domain, you can also bind a port to a local IP:
# Only accessible from the localhost machine itself
127.0.0.1:8080 {
respond "Hello from localhost!"
}
Domain Matching Priority (Routing Priority) #
If you have many site blocks in the Caddyfile, Caddy must decide which block most deserves to process an incoming HTTP request. Caddy uses very strict priority rules to determine the host matching winner.
These priority rules are ordered from most specific (highest priority) to most general (lowest priority):
- Exact Domain with the Longest Path Prefix
- Example:
api.example.com/v1/users
- Example:
- Exact Domain Without a Path Prefix
- Example:
api.example.com
- Example:
- Wildcard Domain with the Longest Subdomain
- Example:
*.sub.example.com
- Example:
- Standard Wildcard Domain
- Example:
*.example.com
- Example:
- Port Catch-All (All Hosts)
- Example:
:443or:80
- Example:
Let’s look at an example implementation in the Caddyfile:
# Block A
*.example.com {
respond "Hello from Wildcard Subdomain!"
}
# Block B
api.example.com {
respond "Hello from the Specific API!"
}
# Block C
api.example.com/v1/auth/* {
respond "Hello from the API Auth Endpoint!"
}
If a client sends a request to:
api.example.com/v1/auth/login-> Matched with Block C (because it has the most specific domain and path prefix).api.example.com/users-> Matched with Block B (because the domain matches exactly and Block C doesn’t meet the/v1/authpath criteria).blog.example.com-> Matched with Block A (because the domain matches the wildcard pattern).
Common Patterns and Use Cases #
1. Safe Non-WWW to WWW Redirect (and vice versa) #
In production, you usually want to settle on one canonical domain for optimal SEO. If a user accesses the non-canonical domain, the server redirects them automatically.
# Non-canonical domain (non-www)
example.com {
# Permanently (301) redirect to the canonical domain, preserving the HTTPS scheme and original request URI
redir https://www.example.com{uri} permanent
}
# Canonical domain (www)
www.example.com {
root * /var/www/html
file_server
}
2. Multi-Environment Configuration (Dev, Staging, Prod) #
You can use environment variables so the same Caddyfile runs on your local development laptop without public HTTPS, and on staging and production servers with Let’s Encrypt HTTPS active.
# Default domain and backend port values are taken from the OS environment
{env.APP_URL} {
reverse_proxy localhost:{env.BACKEND_PORT}
# If locally you don't need Let's Encrypt TLS, you can set
# the APP_URL environment variable to 'http://localhost' or 'localhost'
}
To run on a local development machine:
export APP_URL="localhost"
export BACKEND_PORT="3000"
caddy run
To run on a production server:
export APP_URL="myproduction.com"
export BACKEND_PORT="8080"
caddy run
Site Address Troubleshooting #
Here are the most frequently occurring problems related to site address configuration along with their resolution steps:
1. Caddy Fails to Obtain an SSL Certificate (TLS Handshake Fail) #
If your domain isn’t accessible via HTTPS and the log shows an ACME error message:
- Cause 1: DNS records don’t point to the server.
- Solution: Run
dig +short your-domain.comin your terminal. The returned IP must point to your server’s public IP. If the domain isn’t pointed, Let’s Encrypt can’t verify the HTTP-01 challenge.
- Solution: Run
- Cause 2: Port 80 or 443 is blocked by a firewall.
- Solution: Let’s Encrypt must reach your server on port
80to validate domain ownership. Make sure the firewall (like UFW on Ubuntu, iptables, AWS Security Groups, or Cloudflare Proxy) allows inbound traffic on ports80and443.
- Solution: Let’s Encrypt must reach your server on port
- Cause 3: Using a wildcard without a DNS API token.
- Solution: Check whether you wrote a wildcard domain (
*.domain.com) but forgot to configure the DNS challenge plugin in thetlsblock.
- Solution: Check whether you wrote a wildcard domain (
2. Error: “bind: address already in use” #
When Caddy starts, it shows a port binding failure message:
run: loading initial config: loading new config: http app: start: listening on :80: listen tcp :80: bind: address already in use
- Cause: Another web server or process (like Nginx, Apache, or another Caddy instance running as a service) is currently using port
80or443on your machine. - Solution: Find the process using the port and stop it:
# Find the PID of the process monopolizing port 80/443 sudo lsof -i :80 sudo ss -tlnp | grep ':80' # Stop that service (for example, if Nginx is active) sudo systemctl stop nginx
Summary #
- Automatic HTTPS is only enabled by Caddy if the site address uses a public domain name and doesn’t specify the HTTP port (
80) or thehttp://scheme.- Wildcard domains (
*.example.com) must use the DNS-01 Challenge module in the TLS configuration block so Caddy can prove domain ownership to the certificate authority.- On-Demand TLS enables dynamic SSL certificate issuance at the first TLS handshake. You must use the
askendpoint to validate customer domains and prevent abuse.- The special
localhostdomain triggers Caddy’s internal CA to create trusted local certificates on your development computer.- Use the comma (
,) separator or newlines to point multiple domains at the same site block configuration.- Caddy evaluates request matching by domain specificity level. An exact domain with the longest path prefix always wins over a wildcard domain or catch-all port.