Files
reverse-proxy/deploy/README.md
glm-5.1 c8ab794ef3 Add LICENSE, README, AGENTS.md, and deployment setup guide
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.
2026-06-12 11:42:08 +00:00

6.7 KiB

Deployment

Step-by-step setup guides for running reverse-proxy.

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.10 with your server's public IP
  • Update upstream service definitions to match your infrastructure
  • Adjust the DB_PASSWORD environment variable (use Docker secrets or .env file, 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.0 is rejected unless --allow-wildcard-bind or allow_wildcard_bind = true is set. This prevents accidental exposure on unintended interfaces.
  • The health check endpoint binds to 127.0.0.1 only 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 Server header is stripped from upstream responses and not added by the proxy, reducing server fingerprinting.