Dual MIT/Apache-2.0 license, public-facing README with quick start and config reference, step-by-step deploy/README.md for Docker and systemd setups, and AGENTS.md for LLM-assisted development.
6.7 KiB
Deployment
Step-by-step setup guides for running reverse-proxy.
Docker Deployment (Recommended)
This is the easiest way to get started and provides container-level isolation as a defense-in-depth measure.
1. Build the image
cd /path/to/reverse-proxy
docker build -t reverse-proxy .
2. Create directories on the host
sudo mkdir -p /etc/reverse-proxy
sudo mkdir -p /var/lib/reverse-proxy/acme-cache
sudo mkdir -p /var/log/reverse-proxy
sudo mkdir -p /run/reverse-proxy
3. Create the config file
Create /etc/reverse-proxy/config.toml. For a basic single-domain setup with
Let's Encrypt:
allow_wildcard_bind = true
health_check_port = 9900
[logging]
level = "info"
format = "text"
log_file_path = "/var/log/reverse-proxy/access.log"
[rate_limit]
requests_per_second = 10
burst = 20
[body]
limit_bytes = 104857600
[[listeners]]
bind_addr = "0.0.0.0"
http_port = 80
https_port = 443
[listeners.tls]
mode = "acme"
acme_domains = ["yourdomain.example.com"]
acme_cache_dir = "/var/lib/reverse-proxy/acme-cache"
acme_directory = "production"
acme_contact = "mailto:admin@yourdomain.example.com"
[[listeners.sites]]
host = "yourdomain.example.com"
upstream = "your-backend:8080"
Important: Replace yourdomain.example.com with your actual domain and
your-backend:8080 with your upstream service address. For initial testing,
use acme_directory = "staging" to avoid Let's Encrypt rate limits.
4. Set up Docker Compose
Copy and customize docker-compose.yml:
cp deploy/docker-compose.yml /opt/reverse-proxy/docker-compose.yml
Edit the compose file to:
- Replace
203.0.113.10with your server's public IP - Update upstream service definitions to match your infrastructure
- Adjust the
DB_PASSWORDenvironment variable (use Docker secrets or.envfile, never commit real passwords)
5. Start the proxy
cd /opt/reverse-proxy
docker compose up -d
6. Verify
# Check container health
docker compose ps
# Test health endpoint (from the host)
curl -s http://127.0.0.1:9900/health
# Check logs
docker compose logs reverse-proxy
# Test TLS
curl -v https://yourdomain.example.com/
7. Set up fail2ban
If you want automated IP banning for rate limit violations:
sudo cp deploy/fail2ban/filter.d/reverse-proxy.conf /etc/fail2ban/filter.d/
sudo cp deploy/fail2ban/jail.d/reverse-proxy.conf /etc/fail2ban/jail.d/
sudo systemctl restart fail2ban
Verify fail2ban is watching the logs:
sudo fail2ban-client status reverse-proxy
Bare Metal / systemd Deployment
For running directly on a host without Docker.
1. Build
cargo build --release
# For a fully static binary (no libc dependency):
# cargo build --release --target x86_64-unknown-linux-musl
2. Install
sudo cp target/release/reverse-proxy /usr/local/bin/
sudo cp deploy/reverse-proxy.service /etc/systemd/system/
3. Create config and directories
sudo mkdir -p /etc/reverse-proxy
sudo mkdir -p /var/lib/reverse-proxy/acme-cache
sudo mkdir -p /var/log/reverse-proxy
sudo mkdir -p /run/reverse-proxy
Create /etc/reverse-proxy/config.toml (see example configs in the main
README). With a bare metal deployment, use the server's actual IP as
bind_addr instead of 0.0.0.0:
# Single-domain bare metal example
health_check_port = 9900
[logging]
level = "info"
format = "text"
log_file_path = "/var/log/reverse-proxy/access.log"
[rate_limit]
requests_per_second = 10
burst = 20
[body]
limit_bytes = 104857600
[[listeners]]
bind_addr = "203.0.113.10"
http_port = 80
https_port = 443
[listeners.tls]
mode = "acme"
acme_domains = ["yourdomain.example.com"]
acme_cache_dir = "/var/lib/reverse-proxy/acme-cache"
acme_directory = "production"
acme_contact = "mailto:admin@yourdomain.example.com"
[[listeners.sites]]
host = "yourdomain.example.com"
upstream = "127.0.0.1:3000"
4. Start
sudo systemctl daemon-reload
sudo systemctl enable --now reverse-proxy
5. Verify
# Check service status
systemctl status reverse-proxy
# Test health endpoint
curl -s http://127.0.0.1:9900/health
# Check logs
journalctl -u reverse-proxy -f
6. Reload config
# Via SIGHUP (no feedback)
sudo kill -SIGHUP $(pidof reverse-proxy)
# Via admin socket (returns success/failure JSON)
echo "reload" | socat - UNIX-CONNECT:/run/reverse-proxy/admin.sock
# Check status
echo "status" | socat - UNIX-CONNECT:/run/reverse-proxy/admin.sock
Multi-Domain Setup
Shared IP with SAN certificate
One IP, one listener, multiple domains on a single Let's Encrypt SAN certificate:
[[listeners]]
bind_addr = "203.0.113.10"
[listeners.tls]
mode = "acme"
acme_domains = ["git.example.com", "www.example.com"]
acme_cache_dir = "/var/lib/reverse-proxy/acme-cache"
acme_directory = "production"
acme_contact = "mailto:admin@example.com"
[[listeners.sites]]
host = "git.example.com"
upstream = "127.0.0.1:3000"
[[listeners.sites]]
host = "www.example.com"
upstream = "127.0.0.1:8080"
Dedicated IP per domain
Multiple listeners, each with its own IP and certificate:
[[listeners]]
bind_addr = "203.0.113.10"
[listeners.tls]
mode = "acme"
acme_domains = ["git.example.com"]
acme_cache_dir = "/var/lib/reverse-proxy/acme-cache-git"
acme_directory = "production"
acme_contact = "mailto:admin@example.com"
[[listeners.sites]]
host = "git.example.com"
upstream = "127.0.0.1:3000"
[[listeners]]
bind_addr = "203.0.113.11"
[listeners.tls]
mode = "manual"
cert_path = "/etc/ssl/www.example.com/fullchain.pem"
key_path = "/etc/ssl/www.example.com/privkey.pem"
[[listeners.sites]]
host = "www.example.com"
upstream = "127.0.0.1:8080"
HTTPS Upstream
If your upstream service uses TLS, set upstream_scheme = "https":
[[listeners.sites]]
host = "secure.example.com"
upstream = "10.0.0.5:8443"
upstream_scheme = "https"
The proxy validates upstream TLS certificates using the system's native certificate store.
Security Notes
- The proxy binds to explicit IP addresses by default.
0.0.0.0is rejected unless--allow-wildcard-bindorallow_wildcard_bind = trueis set. This prevents accidental exposure on unintended interfaces. - The health check endpoint binds to
127.0.0.1only and is never exposed on public ports. - The admin socket should be protected by file permissions. It defaults to
/run/reverse-proxy/admin.sock. - Rate limiting is global per-IP (IPv4: /32, IPv6: /64) in the current version. Per-site rate limits may be added later.
- All log output disables ANSI escape codes for fail2ban and container compatibility.
- The
Serverheader is stripped from upstream responses and not added by the proxy, reducing server fingerprinting.