Deploy Caddy Reverse Proxy
Overview
Automatically deploy Caddy reverse proxy server on remote servers with:
- •Automatic SSL Management: Let's Encrypt certificate acquisition and renewal
- •Reverse Proxy Configuration: Route domain traffic to local services (HTTP/WebSocket)
- •Systemd Integration: Auto-start and crash recovery
- •Smart Environment Detection: Automatic system detection and optimal configuration
Prerequisites
Verify before deployment:
- •
Server Access:
- •SSH access configured (supports SSH config aliases)
- •sudo privileges
- •Internet access (for downloading Caddy and obtaining certificates)
- •
DNS Configuration:
- •Domain points to server IP (A record or CNAME)
- •DNS must be propagated before SSL certificate can be obtained
- •
Local Service:
- •Web service already running on server
- •Listening on 127.0.0.1 or localhost (note the port number)
Workflow
1. Parameter Collection
Use AskUserQuestion tool to interactively collect deployment parameters:
**Required Parameters**: - SSH host alias or address (e.g., `dev` or `user@example.com`) - Domain name (e.g., `app.example.com`) - Backend service port (e.g., `8000`, `3000`) **Optional Parameters**: - Whether static file serving is needed (if frontend is bundled separately) - Static file path (e.g., `/var/www/app/dist`) - Routing rules (e.g., `/api` proxies to port A, `/ui` serves static files)
Example Questions:
question: "How do you connect to the server?"
options:
- label: "SSH config alias (Recommended)"
description: "Use host alias from ~/.ssh/config, e.g., 'dev'"
- label: "Full SSH address"
description: "Format: user@hostname or user@ip"
question: "What is the domain name?"
header: "Domain"
question: "What port is the backend service listening on?"
header: "Backend Port"
2. Environment Detection
Connect to server and perform environment checks:
# Check if Caddy is installed which caddy && caddy version # Check CPU architecture (for downloading correct binary) uname -m # x86_64 → amd64, aarch64 → arm64 # Check operating system cat /etc/os-release # Check available users (prefer www-data, create caddy user if not exists) id www-data 2>/dev/null || id caddy 2>/dev/null # Check if backend service is running sudo ss -tlnp | grep <backend_port>
Adaptive Strategy:
- •If Caddy is installed and version >= 2.0, ask whether to reinstall or use existing version
- •If
www-datauser exists, use it; otherwise createcaddysystem user - •If backend service is not running, warn user and ask whether to continue
3. Install Caddy
If Caddy is not installed or needs update:
# Download Caddy binary cd /tmp curl -L "https://caddyserver.com/api/download?os=linux&arch=<arch>" -o caddy chmod +x caddy # Install to system path sudo mv caddy /usr/bin/caddy # Verify installation /usr/bin/caddy version
Architecture Mapping:
- •
x86_64→amd64 - •
aarch64→arm64 - •
armv7l→armv7
4. Create Necessary Directories and Users
# If using www-data user (Debian/Ubuntu default) sudo mkdir -p /etc/caddy /var/lib/caddy sudo chown -R www-data:www-data /etc/caddy /var/lib/caddy # If need to create caddy user (CentOS/RHEL/others) sudo groupadd --system caddy sudo useradd --system --gid caddy \ --create-home --home-dir /var/lib/caddy \ --shell /sbin/nologin \ --comment "Caddy web server" caddy
5. Generate Configuration Files
5.1 Caddyfile
Generate configuration based on user parameters:
Scenario A: Pure Reverse Proxy (Most Common)
example.com {
reverse_proxy http://127.0.0.1:8000
log {
output stdout
}
}
Scenario B: Static Files + API Proxy
example.com {
# Static file serving
handle /ui/* {
root * /var/www/app/dist
try_files {path} /index.html
file_server
}
# API reverse proxy
handle /api/* {
reverse_proxy http://127.0.0.1:8000
}
log {
output stdout
}
}
Scenario C: Multi-Service Proxy
example.com {
# Main application
handle /app/* {
reverse_proxy http://127.0.0.1:3000
}
# Backend API
handle /api/* {
reverse_proxy http://127.0.0.1:8000
}
# WebSocket service
handle /ws {
reverse_proxy http://127.0.0.1:9000
}
# Default route
handle {
reverse_proxy http://127.0.0.1:3000
}
log {
output stdout
}
}
Write configuration file:
sudo tee /etc/caddy/Caddyfile > /dev/null << 'EOF' <generated configuration> EOF # Validate configuration syntax sudo /usr/bin/caddy validate --config /etc/caddy/Caddyfile
5.2 Systemd Service File
Generate service file using assets/caddy.service.template:
sudo tee /etc/systemd/system/caddy.service > /dev/null << 'EOF' [Unit] Description=Caddy Documentation=https://caddyserver.com/docs/ After=network.target network-online.target Requires=network-online.target [Service] Type=notify User=www-data Group=www-data Environment=XDG_DATA_HOME=/var/lib/caddy Environment=XDG_CONFIG_HOME=/var/lib/caddy ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force TimeoutStopSec=5s LimitNOFILE=1048576 LimitNPROC=512 PrivateTmp=true ProtectSystem=full AmbientCapabilities=CAP_NET_BIND_SERVICE [Install] WantedBy=multi-user.target EOF
Key Configuration Notes:
- •
Type=notify: Caddy supports systemd notification, ensuring certificate loading completes before marking as active - •
AmbientCapabilities=CAP_NET_BIND_SERVICE: Allows non-root user to bind ports 80/443 - •
Environment=XDG_DATA_HOME=/var/lib/caddy: Certificate storage path, avoids permission issues - •
LimitNOFILE=1048576: File descriptor limit for high-concurrency scenarios
6. Start Service
# Reload systemd configuration sudo systemctl daemon-reload # Enable auto-start on boot sudo systemctl enable caddy # Start service sudo systemctl start caddy # Wait for SSL certificate acquisition (first start needs 5-10 seconds) sleep 5 # Check service status sudo systemctl status caddy --no-pager
7. Verify Deployment
Perform checks to ensure successful deployment:
# 1. Check Caddy listening ports sudo ss -tlnp | grep caddy # Expected output: 80, 443, 2019 (admin port) # 2. Check backend service sudo ss -tlnp | grep <backend_port> # Ensure backend service is running # 3. Check certificate acquisition logs sudo journalctl -u caddy -n 30 --no-pager | grep -E 'certificate obtained|error' # Expected output: certificate obtained successfully # 4. Test HTTPS access curl -I https://<domain> -m 10 # Expected output: HTTP/2 200
8. Generate Deployment Report
Show deployment results to user:
## ✅ Caddy Deployment Complete ### Deployment Configuration - **Domain**: <domain> - **Backend Service**: 127.0.0.1:<port> ✅ Running - **SSL Certificate**: Let's Encrypt (automatically obtained) - **Protocol Support**: HTTP/2, HTTP/3, WebSocket ### Service Status - Caddy Version: <version> - Listening Ports: 80 (HTTP → HTTPS), 443 (HTTPS), 2019 (admin) - Running User: <user> - Auto-start: Enabled ### Key File Locations - Configuration: `/etc/caddy/Caddyfile` - Service File: `/etc/systemd/system/caddy.service` - Certificate Storage: `/var/lib/caddy/` - Binary: `/usr/bin/caddy` ### Common Commands ```bash # Check service status sudo systemctl status caddy # View live logs sudo journalctl -u caddy -f # Reload configuration (zero-downtime) sudo systemctl reload caddy # Restart service sudo systemctl restart caddy # Validate configuration syntax sudo caddy validate --config /etc/caddy/Caddyfile
Test Results
✅ HTTPS access working ✅ SSL certificate obtained successfully ✅ Reverse proxy working correctly
Your service is now accessible at https://<domain>!
## Troubleshooting ### Common Issues #### 1. Certificate Acquisition Failure **Error**: `storage is probably misconfigured` or `permission denied` **Cause**: Caddy cannot write to certificate storage directory **Solution**: ```bash # Check environment variable configuration sudo systemctl cat caddy | grep Environment # Should include: Environment=XDG_DATA_HOME=/var/lib/caddy # Check directory permissions ls -ld /var/lib/caddy # Should be: drwxr-xr-x www-data www-data # Fix permissions sudo chown -R www-data:www-data /var/lib/caddy sudo systemctl restart caddy
2. Port Binding Failure
Error: bind: permission denied or address already in use
Cause:
- •No permission to bind ports 80/443
- •Port already in use by another service
Solution:
# Check capabilities sudo systemctl cat caddy | grep AmbientCapabilities # Should include: AmbientCapabilities=CAP_NET_BIND_SERVICE # Check port usage sudo ss -tlnp | grep :443 # If occupied by another program, stop it or modify Caddyfile to use different port # If system doesn't support AmbientCapabilities, manually grant permission sudo setcap 'cap_net_bind_service=+ep' /usr/bin/caddy
3. DNS Resolution Failure
Error: no such host or DNS problem: NXDOMAIN
Cause: Domain not correctly pointing to server IP
Solution:
# Check DNS record dig +short <domain> # Should return server public IP # Check server public IP curl -4 ifconfig.me # If DNS not propagated, wait for TTL to expire (usually 5 minutes to 1 hour)
4. Reverse Proxy 502 Error
Error: Accessing domain returns 502 Bad Gateway
Cause: Backend service not running or incorrect listening address
Solution:
# Check backend service status sudo ss -tlnp | grep <backend_port> # Test local access curl http://127.0.0.1:<backend_port> # Check Caddy logs sudo journalctl -u caddy -f # If backend service listens on 0.0.0.0 instead of 127.0.0.1, modify Caddyfile
5. WebSocket Connection Failure
Error: WebSocket handshake fails or connection interrupted
Cause: Caddy 2.x supports WebSocket by default, usually a backend issue
Solution:
# Check backend service WebSocket endpoint
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: test" \
http://127.0.0.1:<backend_port>/ws
# If special configuration needed, add to Caddyfile:
handle /ws {
reverse_proxy http://127.0.0.1:<port> {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
Advanced Configuration
Multi-Domain Configuration
app.example.com {
reverse_proxy http://127.0.0.1:8000
}
api.example.com {
reverse_proxy http://127.0.0.1:9000
}
Custom SSL Certificate Email
{
email admin@example.com
}
example.com {
reverse_proxy http://127.0.0.1:8000
}
Add Security Headers
example.com {
reverse_proxy http://127.0.0.1:8000
header {
# HSTS
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Prevent XSS
X-Content-Type-Options "nosniff"
# Prevent clickjacking
X-Frame-Options "DENY"
# CSP
Content-Security-Policy "default-src 'self'"
}
}
Enable Access Log Files
example.com {
reverse_proxy http://127.0.0.1:8000
log {
output file /var/log/caddy/access.log {
roll_size 100mb
roll_keep 10
}
}
}
Rate Limiting Configuration
example.com {
rate_limit {
zone dynamic {
key {remote_host}
events 100
window 1m
}
}
reverse_proxy http://127.0.0.1:8000
}
Resources
Templates
- •assets/Caddyfile.template - Caddyfile configuration template
- •assets/caddy.service.template - systemd service unit file template
Documentation
- •Caddy Official Documentation
- •Caddyfile Syntax Reference
- •Reverse Proxy Directive
- •Automatic HTTPS Configuration
Maintenance
View Certificate Information
# View certificate storage location ls -la /var/lib/caddy/caddy/certificates/ # View certificate details sudo /usr/bin/caddy list-certificates
Force Certificate Renewal
# Caddy renews automatically, but to manually trigger: sudo systemctl reload caddy
Update Caddy
# Download new version cd /tmp curl -L "https://caddyserver.com/api/download?os=linux&arch=amd64" -o caddy chmod +x caddy # Replace old version sudo systemctl stop caddy sudo mv caddy /usr/bin/caddy sudo systemctl start caddy # Verify version caddy version
Migrate Configuration
# Backup configuration sudo tar -czf caddy-backup-$(date +%Y%m%d).tar.gz \ /etc/caddy /var/lib/caddy /etc/systemd/system/caddy.service # Restore configuration sudo tar -xzf caddy-backup-YYYYMMDD.tar.gz -C / sudo systemctl daemon-reload sudo systemctl restart caddy
Best Practices
- •Verify DNS Before Deployment: Ensure domain resolves to server IP
- •Use Environment Variables: systemd service file must set
XDG_DATA_HOME - •Principle of Least Privilege: Use
www-dataorcaddysystem user, avoid root - •Monitor Logs: Watch logs for 5-10 minutes after first deployment to ensure certificate acquisition
- •Backup Certificates: Certificates stored in
/var/lib/caddy, backup regularly - •Test Restart: After deployment, run
sudo rebootto verify auto-start works - •Firewall Configuration: Ensure ports 80/443 are open (cloud servers need security group configuration)