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.
305 lines
6.7 KiB
Markdown
305 lines
6.7 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
cd /path/to/reverse-proxy
|
|
docker build -t reverse-proxy .
|
|
```
|
|
|
|
### 2. Create directories on the host
|
|
|
|
```bash
|
|
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:
|
|
|
|
```toml
|
|
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`:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
cd /opt/reverse-proxy
|
|
docker compose up -d
|
|
```
|
|
|
|
### 6. Verify
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
sudo fail2ban-client status reverse-proxy
|
|
```
|
|
|
|
## Bare Metal / systemd Deployment
|
|
|
|
For running directly on a host without Docker.
|
|
|
|
### 1. Build
|
|
|
|
```bash
|
|
cargo build --release
|
|
# For a fully static binary (no libc dependency):
|
|
# cargo build --release --target x86_64-unknown-linux-musl
|
|
```
|
|
|
|
### 2. Install
|
|
|
|
```bash
|
|
sudo cp target/release/reverse-proxy /usr/local/bin/
|
|
sudo cp deploy/reverse-proxy.service /etc/systemd/system/
|
|
```
|
|
|
|
### 3. Create config and directories
|
|
|
|
```bash
|
|
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`:
|
|
|
|
```toml
|
|
# 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
|
|
|
|
```bash
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now reverse-proxy
|
|
```
|
|
|
|
### 5. Verify
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```toml
|
|
[[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:
|
|
|
|
```toml
|
|
[[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"`:
|
|
|
|
```toml
|
|
[[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. |