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→amd64aarch64→arm64armv7l→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 activeAmbientCapabilities=CAP_NET_BIND_SERVICE: Allows non-root user to bind ports 80/443Environment=XDG_DATA_HOME=/var/lib/caddy: Certificate storage path, avoids permission issuesLimitNOFILE=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)
Converted and distributed by TomeVault — claim your Tome and manage your conversions.