deploy-caddy-reverse-proxy
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)
More from ichuan/skills
roadmap-management
Minimalist project roadmap management using a position-based priority system in ROADMAP.md. Use when users want to: (1) Create or initialize a project roadmap, (2) Add tasks/features to a roadmap, (3) Update task priorities or status, (4) Reorganize roadmap items, (5) Move tasks between sections (Inbox/Doing/Next Up/Backlog/Done), (6) Clean up or review the roadmap, or any other roadmap planning and tracking activities. Triggered by keywords like 'roadmap', 'task planning', 'project planning', 'milestone', 'priority'.
10crawl4ai-fetch
Fetch any URL and convert it to clean Markdown via a self-hosted crawl4ai server. Use when the user wants to read a webpage, extract article content, summarize a URL, or get the text of a page in a format suitable for an LLM.
1searxng-search
Web search via a self-hosted SearXNG aggregation server. Use when the user asks to search the web, find URLs, look up information online, or research a topic using a search engine. Returns URL, title, and snippet for each result.
1iterative-code-review
>
1pre-commit-review
Comprehensive code review for uncommitted changes before git commit. Use when users want to: (1) Review code changes before committing, (2) Check for security vulnerabilities, bugs, or performance issues, (3) Get feedback on code quality and best practices, (4) Identify issues by severity level. Triggered by phrases like 'review my changes', 'check my code', 'review before commit', 'code review', or similar requests for pre-commit validation.
1