Docs / Core Concepts / Domains & SSL

Domains & SSL

sh0 uses Caddy as its reverse proxy, which means every domain gets automatic HTTPS via Let's Encrypt. No manual certificate management, no cron jobs, no configuration files -- it just works.

How Domains Work

When you assign a domain to an app, sh0 automatically configures Caddy to:

  1. Listen for incoming requests on that domain (ports 80 and 443).
  2. Obtain and install an SSL certificate from Let's Encrypt.
  3. Redirect all HTTP traffic to HTTPS.
  4. Reverse proxy HTTPS traffic to the correct Docker container.
  5. Automatically renew the certificate before it expires.
Flow diagram showing: Internet request to domain.com goes to Caddy (ports 80/443), which terminates SSL and proxies to the Docker container on its internal port
Caddy handles SSL termination and proxying

This entire process is hands-off. Once you add a domain and point its DNS to your server, everything else is automatic.

Default Subdomains

Every app deployed on sh0 Cloud receives a free subdomain under *.sh0.app. This is useful for:

  • Quick testing without configuring DNS
  • Staging environments
  • Preview deployments for pull requests

The subdomain is based on your app name: if your app is named my-api, it will be accessible at my-api.sh0.app.

Note
Default subdomains are fully functional with SSL. They are great for development and testing. For production, you will typically want to use your own custom domain.

Adding a Custom Domain

To add a custom domain to your app:

  1. Navigate to your app and open the Domains tab.
  2. Click Add Domain.
  3. Enter your domain name (e.g., app.example.com or example.com).
  4. Save and configure your DNS (see below).
Add Domain dialog showing a text input for the domain name, with examples of valid formats: 'example.com', 'app.example.com', 'api.example.com'
Adding a custom domain

You can add multiple domains to a single app. All of them will serve the same application. This is useful for:

  • Serving both example.com and www.example.com
  • Supporting multiple domain names that point to the same service
  • Keeping the default subdomain as a fallback alongside your custom domain

DNS Configuration

Point your domain to your sh0 server by adding DNS records at your registrar or DNS provider:

TypeNameValueUse Case
A@Your server's IPRoot domain (example.com)
AappYour server's IPSubdomain (app.example.com)
CNAMEwwwexample.comwww redirect
Example DNS Records
# For app.example.com
Type: A
Name: app
Value: 203.0.113.50
TTL: 300

# For example.com (root domain)
Type: A
Name: @
Value: 203.0.113.50
TTL: 300
DNS Propagation
DNS changes usually propagate within minutes but can take up to 24 hours. You can verify your DNS is correctly configured using dig app.example.com or nslookup app.example.com.

Automatic SSL

SSL certificates are managed entirely by Caddy. There is nothing to configure, no certificates to upload, and no renewal scripts to maintain.

How Let's Encrypt Works

When you add a domain, the following happens automatically:

  1. Challenge -- Caddy initiates an ACME HTTP-01 challenge to prove domain ownership.
  2. Issuance -- Let's Encrypt verifies the challenge and issues a certificate (typically within seconds).
  3. Installation -- Caddy installs the certificate and begins serving HTTPS traffic.
  4. Renewal -- Caddy automatically renews the certificate 30 days before expiration.
Port 80 Must Be Open
The HTTP-01 challenge requires that port 80 is accessible from the internet. If port 80 is blocked by a firewall, Caddy cannot obtain certificates. Make sure ufw allow 80/tcp (or equivalent) is configured.
Domain list showing three domains with their SSL status: 'app.example.com' with a green lock icon and 'Valid until Dec 2026', 'api.example.com' with a green lock, and 'staging.example.com' with a yellow pending icon
SSL certificate status for each domain

Wildcard Domains

Wildcard domains (e.g., *.example.com) allow any subdomain to route to your app. This is useful for:

  • Multi-tenant applications where each tenant gets a subdomain
  • Preview deployments with unique subdomains per branch
  • Geographic or language-based routing
Wildcard SSL Requires DNS Challenge
Wildcard certificates require a DNS-01 challenge instead of HTTP-01. This means you need to configure a DNS provider API key in sh0 settings so Caddy can create TXT records for verification. Supported providers include Cloudflare, Route53, DigitalOcean, and more.
Configure DNS Provider for Wildcards
# In sh0 Settings > SSL, configure your DNS provider
# Example for Cloudflare:
DNS_PROVIDER=cloudflare
CLOUDFLARE_API_TOKEN=your-api-token

SSL Certificate Status

The Domains section of each app shows the SSL status for every configured domain:

StatusMeaning
ActiveCertificate is valid and serving HTTPS traffic.
PendingNo certificate yet. Since v1.6.25 issuance is on demand: the certificate is requested at the first TLS handshake on the domain, so a domain stays Pending until someone actually connects to it. Hover the badge to read why it is still pending.
RenewingCertificate is being renewed (30 days before expiration).
FailedCertificate provisioning failed. Check DNS and firewall settings.

Two more rules since v1.6.25. An Active domain whose certificate disappears from disk goes back to Pending after two consecutive checks, never on a single read. And the certificate expiry date is now read from the certificate itself, so expiry alerts finally have a value to work with.

Troubleshooting

If your domain is not working or the SSL certificate fails to provision, check these common issues:

DNS not pointing to your server

Run dig +short your-domain.com and verify it returns your server's IP address. If it does not, update your DNS records and wait for propagation.

Port 80 is blocked

Let's Encrypt needs port 80 to verify domain ownership. Ensure your firewall allows incoming TCP traffic on port 80. Check with sudo ufw status.

Another service is using port 80/443

If Nginx, Apache, or another web server is running, it may conflict with Caddy. Stop the other service: sudo systemctl stop nginx.

Rate limit exceeded

Let's Encrypt has rate limits (50 certificates per domain per week). If you hit the limit, wait or use the staging environment for testing. Caddy logs will indicate rate limit errors.

Debugging Commands
# Check if DNS resolves correctly
dig +short app.example.com

# Test if port 80 is accessible from outside
curl -I http://app.example.com

# Check Caddy logs for certificate errors
journalctl -u sh0 | grep -i "certificate\|tls\|acme"