
Deploy Caddy-Proxy
Production-Ready Reverse Proxy for Private Networking & Web Socket
Caddy-Proxy
Just deployed
Deploy and Host
About Hosting
Hosting refers to running your Caddy proxy instance on a cloud platform like Railway, where it can serve as an entry point for incoming traffic. Railway handles the underlying infrastructure, including servers, networking, and SSL termination, allowing you to focus on configuration. This template uses Caddy's automatic HTTPS capabilities (though Railway manages external SSL), private networking for internal service communication, and environment variables for easy setup. It's designed for containerized deployment via Docker, ensuring consistency across environments.
Why Deploy
Deploying a Caddy proxy on Railway offers several benefits:
- Simplified Routing: Centralize traffic management for multiple services without exposing them directly to the internet.
- Security Enhancements: Leverage Caddy's built-in security features like automatic TLS (integrated with Railway's SSL) and headers to protect against common web vulnerabilities.
Common Use Cases
This Caddy proxy template is versatile and suits various scenarios:
- API Gateway: Route requests to multiple backend APIs while handling authentication, rate limiting, and logging in one place.
- Frontend Serving with Backend Proxy: Serve static frontend assets and proxy dynamic API calls, ideal for single-page applications (SPAs).
- WebSocket-Enabled Apps: Proxy real-time features like chat apps or live updates without additional configuration.
- Internal Tool Exposure: Securely expose tools like Jupyter Notebooks or admin dashboards to the public internet.
- Multi-Service Orchestration: Manage traffic for complex setups involving databases, queues, and workers in a Railway project.
- Legacy App Modernization: Proxy older services to add modern features like compression and security headers.
Dependencies for
To run this Caddy proxy template, you'll need the following:
- Runtime Environment: Docker-compatible host (provided by Railway).
- Build Tools: Basic knowledge of Dockerfiles and environment variables; no additional installations required beyond what's in the template.
- Configuration Files: A
Caddyfilefor custom routing (optional; defaults to simple reverse proxy).
Deployment Dependencies
- Environment Variables: As listed in the Configuration section;
PROXY_TARGETis mandatory. - Backend Services: At least one internal or external service to proxy to (e.g., another Railway service).
- Optional: Custom domain and DNS provider for public exposure beyond Railway's generated domains.
⚙️ Configuration
Required Environment Variables
PROXY_TARGET - The backend service to proxy to
Optional Environment Variables
PORT- Port to listen on (Railway provides this automatically)LOG_LEVEL- Logging level: DEBUG, INFO, WARN, ERROR (default: INFO)
🔧 Usage Examples
1. Proxy to Internal Railway Service (Private Network)
Most common use case - proxying to another service in your Railway project:
# Using Railway's private networking (recommended)
PROXY_TARGET=http://api.railway.internal:3000
# Or using reference variables (also recommended)
PROXY_TARGET=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:3000
Important Notes:
- Private networking uses
.railway.internaldomain - Always specify the port your backend service listens on
- Use
http://(nothttps://) for internal connections - Private network is isolated per project/environment
2. Proxy to External Service
PROXY_TARGET=https://api.external-service.com
3. Open Notebook (Jupyter)
PROXY_TARGET=http://notebook.railway.internal:8888
4. Huly Platform
PROXY_TARGET=http://huly.railway.internal:8080
5. Multiple Backend Services (Custom Caddyfile)
For complex routing, create a custom Caddyfile:
:{$PORT:8080} {
# Route API requests
handle /api/* {
reverse_proxy http://api.railway.internal:3000
}
# Route WebSocket connections
handle /ws/* {
reverse_proxy http://websocket.railway.internal:8080
}
# Default to frontend
handle {
reverse_proxy http://frontend.railway.internal:5173
}
}
🌐 Networking on Railway
Private Networking (Service-to-Service)
When to use: Communication between services in the same Railway project
- Use
servicename.railway.internal:PORTformat - Only accessible within your project/environment
- IPv4 and IPv6 supported (new environments)
- Legacy environments (pre-Oct 2025) are IPv6 only Example Configuration:
# Direct reference
PROXY_TARGET=http://backend.railway.internal:8080
# Using Railway variables
PROXY_TARGET=http://${{backend.RAILWAY_PRIVATE_DOMAIN}}:${{backend.PORT}}
Public Networking (Internet Access)
When to use: Making your Caddy proxy accessible from the internet
- Generate Railway Domain:
- Go to service settings → Networking → Public Networking
- Click "Generate Domain"
- Railway will provide a domain like
yourapp.up.railway.app
- Add Custom Domain (Optional):
- Click "+ Custom Domain" in service settings
- Add your domain (e.g.,
proxy.yourdomain.com) - Create a CNAME record pointing to Railway's provided value
- Railway automatically issues SSL certificate Important:
- Caddy listens on the
PORTvariable (Railway provides this) - Set
auto_https offin Caddyfile (Railway handles SSL) - Use
http://for internal connections, Railway handleshttps://externally
🎯 Common Patterns
Pattern 1: API Gateway
Expose internal microservices through a single public endpoint:
Internet → Caddy (public) → Internal Services (private)
↓
https://api.yourdomain.com
├─ /users → http://users-service.railway.internal:3000
├─ /orders → http://orders-service.railway.internal:3001
└─ /products → http://products-service.railway.internal:3002
Pattern 2: Frontend + Backend
Serve frontend and proxy API requests:
Internet → Caddy (public)
↓
├─ / → http://frontend.railway.internal:3000
└─ /api/* → http://backend.railway.internal:8080
Pattern 3: WebSocket Proxy
Proxy WebSocket connections for real-time apps:
PROXY_TARGET=http://realtime-app.railway.internal:8080
Caddy automatically handles WebSocket upgrades.
✨ Features
- ✅ Private Network Support - Seamless Railway service-to-service communication
- ✅ Auto SSL - Railway provides HTTPS automatically
- ✅ WebSocket Support - Real-time applications work out of the box
- ✅ Compression - Automatic gzip compression
- ✅ Security Headers - XSS, clickjacking, and content-type protection
- ✅ CORS Ready - Pre-configured CORS headers
- ✅ Health Checks - Built-in
/healthendpoint - ✅ Request Logging - JSON formatted logs
- ✅ IPv4/IPv6 - Dual-stack support
Common Issues
1. "Connection refused" or "dial tcp: lookup failed"
- Verify backend service is running
- Check
PROXY_TARGETformat:http://service.railway.internal:PORT - Ensure backend service is in the same project/environment
- Confirm backend is listening on the correct port 2. "502 Bad Gateway"
- Backend service may not be listening on
::(all interfaces) - Check backend service logs
- Verify backend health endpoint responds 3. WebSocket not working
- Ensure
ConnectionandUpgradeheaders are preserved - Backend must support WebSocket protocol
- Check if backend is properly handling WebSocket handshake 4. Custom domain not working
- Verify CNAME record is correctly configured
- Wait up to 72 hours for DNS propagation
- Check Railway dashboard shows green checkmark
- If using Cloudflare, set SSL/TLS mode to "Full"
🔐 Security Best Practices
- Private Services - Keep backend services on private network only
- CORS Configuration - Restrict allowed origins in production
- Rate Limiting - Add rate limiting for public endpoints (see advanced config)
- Security Headers - Template includes basic security headers
- Environment Variables - Never commit sensitive values to git
📚 Advanced Configuration
Rate Limiting
Add rate limiting to your Caddyfile:
:{$PORT:8080} {
handle {
rate_limit {
zone static {
key static
events 1000
window 1m
}
zone dynamic {
key {remote_host}
events 100
window 1m
}
}
reverse_proxy {$PROXY_TARGET}
}
}
Custom Error Pages
handle_errors {
@404 {
expression {http.error.status_code} == 404
}
rewrite @404 /404.html
@5xx {
expression {http.error.status_code} >= 500
}
rewrite @5xx /500.html
file_server
}
Path-Based Routing
:{$PORT:8080} {
# Strip /api prefix before proxying
handle /api/* {
uri strip_prefix /api
reverse_proxy http://backend.railway.internal:8080
}
# Keep path as-is
handle /webhooks/* {
reverse_proxy http://webhook-handler.railway.internal:3000
}
}
📖 References
🤝 Contributing
Issues and pull requests welcome! Feel free to customize this template for your needs.
📝 License
MIT License - Feel free to use this template for any project.
Template Content
Caddy-Proxy
AnarchistManifesto/Caddy-Proxy