Deploy Caddy-Proxy

Production-Ready Reverse Proxy for Private Networking & Web Socket

Deploy Caddy-Proxy

Just deployed

Deploy and Host

Deploy on Railway

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 Caddyfile for custom routing (optional; defaults to simple reverse proxy).

Deployment Dependencies

  • Environment Variables: As listed in the Configuration section; PROXY_TARGET is 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.internal domain
  • Always specify the port your backend service listens on
  • Use http:// (not https://) 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:PORT format
  • 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

  1. Generate Railway Domain:
    • Go to service settings → Networking → Public Networking
    • Click "Generate Domain"
    • Railway will provide a domain like yourapp.up.railway.app
  2. 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 PORT variable (Railway provides this)
  • Set auto_https off in Caddyfile (Railway handles SSL)
  • Use http:// for internal connections, Railway handles https:// 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 /health endpoint
  • 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_TARGET format: 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 Connection and Upgrade headers 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

  1. Private Services - Keep backend services on private network only
  2. CORS Configuration - Restrict allowed origins in production
  3. Rate Limiting - Add rate limiting for public endpoints (see advanced config)
  4. Security Headers - Template includes basic security headers
  5. 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

More templates in this category

View Template
Foundry Virtual Tabletop
A Self-Hosted & Modern Roleplaying Platform

View Template
(v1) Simple Medusa Backend
Deploy an ecommerce backend and admin using Medusa

View Template
peppermint
Docker-compose port for peppermint.sh