Docker-based reverse proxy setup using nginx-proxy for automatic service discovery and routing. This setup allows you to run multiple services on a single server and route traffic based on domain names.
This project provides a centralized reverse proxy solution that:
- Automatically discovers and configures routing for Docker containers
- Handles HTTP and HTTPS traffic on ports 80 and 443
- Supports CORS for cross-origin requests
- Integrates with Cloudflare for SSL/TLS termination (default)
- Optionally supports Let's Encrypt for direct SSL certificate management
The reverse proxy uses jwilder/nginx-proxy which automatically generates nginx configuration by monitoring Docker containers. When a container starts with VIRTUAL_HOST environment variable, the proxy automatically routes traffic to it.
Key Components:
- reverse-proxy: Main nginx-proxy container that handles routing
- acme-companion (optional): Let's Encrypt certificate management
- External network:
reverse-proxynetwork for service communication
- Docker and Docker Compose installed
- Ports 80 and 443 available on the host
- Domain names configured (DNS pointing to your server)
-
Create the external Docker network:
docker network create reverse-proxy
-
Start the reverse proxy:
docker compose up -d
-
Verify it's running:
docker compose ps docker compose logs -f reverse-proxy
To proxy a service through this reverse proxy, configure it in a separate docker-compose file:
services:
your-app:
image: your-image:latest
environment:
- VIRTUAL_HOST=yourdomain.com
- VIRTUAL_PORT=8080 # Only if app doesn't use port 80
networks:
- reverse-proxy
networks:
reverse-proxy:
external: trueThe reverse proxy will automatically detect the new container and configure routing.
By default, this setup is designed to work behind Cloudflare:
- Configure your domain's DNS in Cloudflare to point to your server
- Set SSL/TLS mode to "Full" or "Full (strict)" in Cloudflare dashboard
- Cloudflare handles SSL termination and forwards traffic to the proxy
To use Let's Encrypt instead of Cloudflare:
- Uncomment the
acme-companionservice indocker-compose.yml - Uncomment the
./nginx/certsvolume mount in the reverse-proxy service - Update
DEFAULT_EMAILwith your email address - Restart the reverse proxy:
docker compose up -d - Add
LETSENCRYPT_HOSTenvironment variable to your services:environment: - VIRTUAL_HOST=yourdomain.com - LETSENCRYPT_HOST=yourdomain.com
Important: When using Let's Encrypt, your server must be directly accessible on ports 80 and 443 (not proxied through Cloudflare).
Certificate Storage: SSL certificates will be stored in nginx/certs/ and ACME challenge files in nginx/acme/. These directories are in .gitignore for security.
reverse-proxy/
├── docker-compose.yml # Main configuration
├── nginx/
│ ├── conf.d/
│ │ ├── 00-proxy.conf # Global proxy settings
│ │ └── default-site.conf # Default server configuration
│ ├── vhost.d/
│ │ └── default # Security headers and CORS
│ ├── html/
│ │ └── index.html # Custom default page
│ ├── certs/ # SSL certificates (when using Let's Encrypt)
│ └── acme/ # ACME challenge files (when using Let's Encrypt)
└── README.md
Main reverse proxy configuration with service definitions and network setup.
Global nginx proxy settings applied to all virtual hosts:
Security:
- Server tokens disabled (hides nginx version)
- Invalid headers ignored
Client Settings:
- Unlimited body size for large uploads
- 512KB body buffer size
- Optimized header buffers
Timeouts:
- Client timeouts: 20 seconds (body and headers)
- Proxy timeouts: 120 seconds (connect, send, read)
- Keepalive: 15 seconds
Performance:
- Gzip compression enabled for text, JSON, CSS, JavaScript, fonts, and SVG
- Compression level 6 (balanced between speed and size)
- Proxy buffering optimized with 8x4KB buffers
Default server configuration that serves a fallback page when no VIRTUAL_HOST matches the request. This prevents nginx error pages from being shown to end users.
Security headers and CORS configuration applied to all virtual hosts:
Security Headers:
X-Frame-Options: SAMEORIGIN- Prevents clickjacking attacksX-Content-Type-Options: nosniff- Prevents MIME type sniffingX-XSS-Protection: 1; mode=block- Enables XSS filterReferrer-Policy: strict-origin-when-cross-origin- Controls referrer information- HSTS available (commented by default, enable if using Let's Encrypt)
CORS Configuration:
- Allows cross-origin requests with credentials
- Supports all common HTTP methods (GET, POST, PUT, PATCH, DELETE, OPTIONS)
- Custom headers allowed including "Context"
- Preflight requests cached for 24 hours
- Origin-based access control
Additional Protection:
- Rate limiting: 10 requests/second per IP (burst of 20 allowed)
- Hidden files blocked (
.env,.git,.htaccess, etc.)
Custom default page displayed when accessing the server without a configured service. Features:
- Professional, responsive design
- Clear instructions for configuration
- Neutral color scheme with gradient background
- Mobile-friendly layout
Directory where SSL certificates are stored by acme-companion. This directory is ignored by git for security.
Directory for ACME challenge files and acme.sh configuration. This directory is ignored by git.
# Start the reverse proxy
docker compose up -d
# Stop the reverse proxy
docker compose down
# View logs
docker compose logs -f reverse-proxy
# Restart after configuration changes
docker compose restart reverse-proxy
# Check status
docker compose psSeeing the default "Reverse Proxy Configured" page:
- This means the reverse proxy is working but no service is configured for this domain
- Verify the container has
VIRTUAL_HOSTenvironment variable set - Confirm the container is connected to the
reverse-proxynetwork - Check that the domain in
VIRTUAL_HOSTmatches the URL you're accessing
Service not accessible:
- Verify the container is connected to the
reverse-proxynetwork - Check that
VIRTUAL_HOSTis set correctly - Review logs:
docker compose logs reverse-proxy
SSL issues:
- If using Cloudflare, ensure SSL mode is "Full" or "Full (strict)"
- If using Let's Encrypt, verify ports 80/443 are publicly accessible
- Check that DNS is properly configured
This reverse proxy includes multiple security layers:
- Rate limiting: 10 requests/second per IP with burst capacity of 20
- Automatic HTTP 429 response when limits are exceeded
- Connection limiting per IP address
Modern security headers are automatically applied to all responses to protect against common web vulnerabilities:
- Clickjacking protection: X-Frame-Options prevents your site from being embedded in iframes
- MIME sniffing prevention: X-Content-Type-Options prevents browsers from MIME-sniffing
- XSS protection: X-XSS-Protection enables browser XSS filters
- Referrer control: Strict referrer policy to protect user privacy
- Hidden files (
.env,.git, etc.) automatically blocked - Invalid HTTP headers rejected
- Server version information hidden from responses
- Gzip compression reduces bandwidth by 60-80% for text content
- Optimized buffer sizes for handling large requests
- Efficient proxy buffering for backend communication
- Keepalive connections reduce latency
Edit nginx/conf.d/00-proxy.conf and modify:
client_max_body_size 100M; # Change to your desired limitEdit nginx/vhost.d/default to adjust rate limits:
limit_req zone=general burst=20 nodelay; # Adjust burst valueOr edit nginx/conf.d/00-proxy.conf to change the base rate:
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; # Adjust rateIf using Let's Encrypt instead of Cloudflare, uncomment in nginx/vhost.d/default:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;To restrict CORS to specific origins instead of allowing all, edit nginx/vhost.d/default:
# Replace "$http_origin" with your specific domain
add_header 'Access-Control-Allow-Origin' "https://yourdomain.com" always;This is a configuration repository for open-source components. The nginx-proxy and acme-companion images are subject to their respective licenses.