Skip to main content

Caddy Reverse Proxy

Uncloud uses Caddy as a reverse proxy to expose your services to the internet. Caddy automatically handles TLS certificate provisioning, renewal, and HTTPS redirection.

How It Works

When you initialize a cluster or add a machine, Uncloud:
  1. Deploys Caddy in global mode across all machines
  2. Configures Caddy to watch cluster state for services with published ports
  3. Automatically provisions TLS certificates for exposed domains
  4. Routes incoming HTTP/HTTPS traffic to backend containers
Caddy runs as a container on each machine, making every machine capable of handling ingress traffic.

Publishing Services

You can expose services using the -p/--publish flag or in your compose file.

Using the CLI

# Publish container port 8000 as HTTPS on a custom domain
uc run -p myapp.example.com:8000/https myimage:latest

# Use HTTP instead (not recommended for production)
uc run -p myapp.example.com:8000/http myimage:latest

# Publish on a specific load balancer port
uc run -p myapp.example.com:8080:8000/https myimage:latest

Using Docker Compose

services:
  web:
    image: nginx:latest
    ports:
      - "myapp.example.com:80/https"
This exposes port 80 from the container as HTTPS on myapp.example.com.
If you don’t specify a protocol, Uncloud defaults to HTTPS for security.

Automatic HTTPS with Let’s Encrypt

Caddy automatically obtains and renews TLS certificates from Let’s Encrypt for all exposed domains.

Certificate Provisioning

When you publish a service with a custom domain:
  1. Caddy detects the new service and domain from cluster state
  2. Initiates ACME challenge with Let’s Encrypt
  3. Obtains TLS certificate (usually takes a few seconds)
  4. Configures HTTPS listener with the certificate
  5. Automatically redirects HTTP to HTTPS
For the ACME challenge to succeed, your domain must resolve to your machine’s public IP via DNS A records.

Certificate Renewal

Caddy automatically renews certificates:
  • Certificates are checked daily
  • Renewal begins 30 days before expiration
  • Renewal is fully automatic, no manual intervention needed
  • Zero downtime during renewal

Certificate Storage

Certificates are stored in:
  • Local storage on each machine running Caddy
  • Caddy container volume at /data/caddy
If you remove and recreate the Caddy container, it will need to re-obtain certificates. This is usually fast but may hit Let’s Encrypt rate limits if done repeatedly.

Managed DNS with uncld.dev

Uncloud provides free managed DNS for quick deployments without configuring your own DNS.

Cluster Domain

When you initialize a cluster, Uncloud reserves a unique subdomain:
$ uc machine init root@your-server
...
Reserved cluster domain: xuw3xd.uncld.dev
All services in your cluster can use this domain:
# Services automatically get *.cluster-id.uncld.dev
uc run -p api.xuw3xd.uncld.dev:8000/https myapp:latest

Automatic DNS Updates

Uncloud automatically manages DNS records:
  • When machines running Caddy come online, their IPs are added to DNS
  • When machines go offline, their IPs are removed
  • DNS updates propagate within seconds
  • A records point to all internet-reachable machines running Caddy
Managed DNS uses round-robin DNS for simple load distribution across multiple machines.

Service Subdomain

You can publish services without specifying the full domain:
# Short form - uses cluster domain automatically
uc run -p api:8000/https myapp:latest
# Creates: api.xuw3xd.uncld.dev

Custom Domains

To use your own domain, add DNS records pointing to your machines.

DNS Configuration

  1. Get your machine IPs:
uc machine ls
# Note the PUBLIC IP column
  1. Create DNS A records in your DNS provider:
myapp.example.com.  A  203.0.113.10
myapp.example.com.  A  198.51.100.20
Add one A record for each machine running Caddy.
  1. Deploy your service:
uc run -p myapp.example.com:8000/https myimage:latest
  1. Wait for DNS propagation (typically 1-5 minutes)
  2. Access your service at https://myapp.example.com
Caddy will automatically obtain a Let’s Encrypt certificate for your custom domain.

Wildcard Domains

You can use wildcard DNS with custom domains:
  1. Create a wildcard A record:
*.apps.example.com.  A  203.0.113.10
  1. Publish services with subdomains:
uc run -p api.apps.example.com:8000/https api:latest
uc run -p web.apps.example.com:3000/https web:latest
Both services will get HTTPS certificates automatically.

Load Balancing

Caddy automatically load balances across service replicas.

How Load Balancing Works

services:
  api:
    image: myapp/api
    deploy:
      replicas: 3
    ports:
      - "api.example.com:8000/https"
With this configuration:
  1. Three containers start across available machines
  2. Caddy discovers all container IPs via DNS (api.internal)
  3. Incoming requests are distributed across all three containers
  4. Health checks automatically remove unhealthy containers from rotation

Load Balancing Strategy

Caddy uses round-robin load balancing by default:
  • Each request goes to the next container in rotation
  • Simple and fair distribution
  • No sticky sessions or IP-based affinity
For more advanced load balancing strategies, you can use Caddy’s custom configuration.

Health Checks

Caddy integrates with Docker health checks to avoid sending traffic to unhealthy containers.

Configure Health Checks

services:
  api:
    image: myapp/api
    ports:
      - "api.example.com:8000/https"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
Caddy will:
  • Monitor container health status
  • Automatically remove unhealthy containers from load balancing
  • Restore containers when they become healthy again
Health checks are configured at the Docker level, not in Caddy. Caddy reads the health status from cluster state.

Multiple Machines and High Availability

Since Caddy runs in global mode, every machine can handle ingress traffic.

DNS-Based Failover

With multiple A records:
api.example.com.  A  203.0.113.10
api.example.com.  A  198.51.100.20
Clients will:
  • Try the first IP
  • Automatically failover to second IP if first is unreachable
  • This provides basic high availability

Geographic Distribution

Spread machines across regions:
uc machine add --name us-west [email protected]
uc machine add --name eu-central [email protected]
uc machine add --name asia-pacific [email protected]
All machines can serve requests, providing:
  • Lower latency for users worldwide
  • Better fault tolerance
  • Continued operation if a region goes down
Uncloud’s managed DNS automatically updates records to only include reachable machines.

Advanced Caddy Configuration

For advanced use cases, you can provide custom Caddy configuration.

Custom Caddyfile

services:
  web:
    image: nginx:latest
    labels:
      uncloud.caddy: |
        myapp.example.com {
          reverse_proxy web.internal:80
          encode gzip
          header {
            X-Frame-Options "SAMEORIGIN"
            X-Content-Type-Options "nosniff"
          }
        }
This gives you full control over Caddy’s behavior for the service.
Custom Caddy configuration is an advanced feature. Make sure you understand Caddyfile syntax before using it.

Publishing Non-HTTP Services

Caddy is designed for HTTP/HTTPS traffic. For TCP/UDP services, use host mode ports:
services:
  database:
    image: postgres:15
    ports:
      - "5432:5432/tcp@host"
This publishes port 5432 directly on the host, bypassing Caddy. See the Compose Services documentation for more details on host mode ports.

Viewing Endpoints

List all exposed service endpoints:
uc ls
Output shows:
  • Service name
  • Exposed URLs (both custom domains and cluster domains)
  • Backend container ports
NAME       MODE         CONTAINERS  IMAGES           ENDPOINTS
api-3f2a   replicated   2/2         myapp/api:v1.2   • https://api.example.com → :8000
                                                     • https://api.xuw3xd.uncld.dev → :8000

Troubleshooting

Certificate Errors

If HTTPS isn’t working:
  1. Check DNS resolution:
dig myapp.example.com
# Should return your machine's public IP
  1. Check Caddy logs:
uc logs caddy
Look for ACME errors or certificate provisioning failures.
  1. Verify port 443 is accessible:
nc -zv your-machine-ip 443

Connection Refused

If you get “connection refused”:
  1. Verify service is running: uc ls
  2. Check container is healthy: uc ps service-name
  3. Test internal connectivity: curl http://service.internal:port from another container
  4. Check Caddy configuration: uc logs caddy

SSL Certificate Rate Limits

Let’s Encrypt has rate limits:
  • 50 certificates per domain per week
  • 5 duplicate certificates per week
If you hit limits:
Be careful with repeated deployments during testing to avoid hitting Let’s Encrypt rate limits. Use staging endpoints or the cluster domain for testing.

Further Reading

Services

Learn about service deployment and scaling

Networking

Understand how traffic routes to containers

DNS Management

Step-by-step guide to using your own domains

Caddy Documentation

Official Caddy documentation for advanced features

Build docs developers (and LLMs) love