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:
- Listen for incoming requests on that domain (ports 80 and 443).
- Obtain and install an SSL certificate from Let's Encrypt.
- Redirect all HTTP traffic to HTTPS.
- Reverse proxy HTTPS traffic to the correct Docker container.
- Automatically renew the certificate before it expires.
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.
Adding a Custom Domain
To add a custom domain to your app:
- Navigate to your app and open the Domains tab.
- Click Add Domain.
- Enter your domain name (e.g.,
app.example.comorexample.com). - Save and configure your DNS (see below).
You can add multiple domains to a single app. All of them will serve the same application. This is useful for:
- Serving both
example.comandwww.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:
| Type | Name | Value | Use Case |
|---|---|---|---|
| A | @ | Your server's IP | Root domain (example.com) |
| A | app | Your server's IP | Subdomain (app.example.com) |
| CNAME | www | example.com | www redirect |
# 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: 300dig 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:
- Challenge -- Caddy initiates an ACME HTTP-01 challenge to prove domain ownership.
- Issuance -- Let's Encrypt verifies the challenge and issues a certificate (typically within seconds).
- Installation -- Caddy installs the certificate and begins serving HTTPS traffic.
- Renewal -- Caddy automatically renews the certificate 30 days before expiration.
ufw allow 80/tcp (or equivalent) is configured.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
# In sh0 Settings > SSL, configure your DNS provider
# Example for Cloudflare:
DNS_PROVIDER=cloudflare
CLOUDFLARE_API_TOKEN=your-api-tokenSSL Certificate Status
The Domains section of each app shows the SSL status for every configured domain:
| Status | Meaning |
|---|---|
| Active | Certificate is valid and serving HTTPS traffic. |
| Pending | No 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. |
| Renewing | Certificate is being renewed (30 days before expiration). |
| Failed | Certificate 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.
# 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"