A simple, fast reverse proxy and web server written in Rust.
Designed to be easy to understand and use — simpler than nginx for common tasks.
┌────────────────────────────────────┐
Browser ──────▶│ curf (port 80 / 443) │──────▶ Your app (port 3000)
│ • TLS termination │
│ • Load balancing │──────▶ Your app (port 3001)
│ • Static files │
│ • Rate limiting + WAF │──────▶ Your app (port 3002)
└────────────────────────────────────┘
- Reverse proxy — forward requests to one or many backend servers
- Load balancing — round-robin, least-connections, or IP hash
- Circuit breaker — automatically routes around broken backends
- TLS (HTTPS) — multi-domain SNI support with Let's Encrypt certificates
- Static files — serve HTML/CSS/JS directly, with caching headers
- HTTP → HTTPS redirect — one line of config
- Rate limiting — per-IP token-bucket limiter
- Basic WAF — blocks SQLi, XSS, and path-traversal patterns
- WebSocket — transparently proxied
- Health checks — periodic checks; unhealthy backends are skipped
# From source (requires Rust — https://rustup.rs)
git clone https://github.com/jebngg/curf
cd curf
cargo build --release
sudo cp target/release/curf /usr/local/bin/curfThe smallest possible config (curf.yml):
domains:
localhost:
backends:
- http://127.0.0.1:3000That's it. curf will listen on port 80 and forward everything to your app on port 3000.
See examples/curf.yml for a fully annotated example with TLS, load balancing, and static files.
# Default config file is curf.yml in the current directory
curf
# Or specify a path
curf --config /etc/curf/curf.yml
# Override the HTTP port without editing the config
curf --http-port 8080Control log verbosity with the RUST_LOG environment variable:
RUST_LOG=debug curf # verbose — shows every request
RUST_LOG=warn curf # quiet — only warnings and errorsAll settings are in a single YAML file. Only domains is required; everything else has a sensible default.
server:
http_port: 80 # HTTP port (default: 80)
https_port: 443 # HTTPS port (default: 443)
timeout_secs: 30 # Request timeout in seconds (default: 30)
max_connections: 10000 # Max simultaneous connections (default: 10000)
max_connections_per_ip: 100 # Per-IP limit (default: 100)
rate_limit_rps: 100 # Max requests/sec per IP — 0 disables (default: 100)
rate_limit_burst: 200 # Burst allowance (default: 200)server:
security:
waf_sqli: true # Block SQL injection patterns (default: true)
waf_xss: true # Block XSS patterns (default: true)
waf_path_traversal: true # Block ../ traversal (default: true)
block_tls_abusers: true # Block IPs with many TLS failures (default: true)
max_tls_failures: 10 # Failures before blocking (default: 10)
block_empty_user_agent: false # Block empty User-Agent (default: false)Each key is a domain name matched against the HTTP Host header.
domains:
example.com:
backends:
- http://127.0.0.1:3000 # One backend
# or multiple for load balancing:
- http://127.0.0.1:3001
- http://127.0.0.1:3002
load_balance: round_robin # round_robin | least_connections | ip_hash tls:
cert: /path/to/fullchain.pem
key: /path/to/privkey.pem redirect_to_https: true static_files:
root: /var/www/mysite # absolute path
index:
- index.html
autoindex: false # directory listingYou can combine static_files and backends — curf serves the file if it exists, otherwise proxies to the backend. This is perfect for single-page apps.
health_check:
enabled: true
interval_secs: 15
timeout_secs: 5
path: /health headers:
- name: Strict-Transport-Security
value: "max-age=31536000"
- name: X-Frame-Options
value: SAMEORIGINCreate /etc/systemd/system/curf.service:
[Unit]
Description=curf reverse proxy
After=network.target
[Service]
Type=simple
User=www-data
ExecStart=/usr/local/bin/curf --config /etc/curf/curf.yml
Restart=on-failure
RestartSec=5s
# Allow binding to ports 80 and 443 without root
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now curf
sudo systemctl status curf# Install certbot
sudo apt install certbot
# Get a certificate (temporarily stop curf if it's running on port 80)
sudo certbot certonly --standalone -d example.com
# Certificates are placed in:
# /etc/letsencrypt/live/example.com/fullchain.pem
# /etc/letsencrypt/live/example.com/privkey.pemThen in your curf.yml:
domains:
example.com:
backends:
- http://127.0.0.1:3000
redirect_to_https: true
tls:
cert: /etc/letsencrypt/live/example.com/fullchain.pem
key: /etc/letsencrypt/live/example.com/privkey.pem| Feature | nginx | curf |
|---|---|---|
| Config format | nginx.conf (custom syntax) | YAML |
| Multiple domains | server { } blocks |
keys under domains: |
| Reverse proxy | proxy_pass |
backends: list |
| Load balancing | upstream { } block |
load_balance: round_robin |
| Static files | root + try_files |
static_files.root |
| TLS | ssl_certificate etc. |
tls.cert + tls.key |
| Reload config | nginx -s reload |
restart process (hot-reload planned) |
| Modules | many | built-in features only |
curf does less than nginx by design — it covers the most common use cases with the simplest config possible.
# Debug build (faster to compile, good for development)
cargo build
# Release build (optimized, for production)
cargo build --releaseMIT — see LICENSE
Pull requests are welcome. Please keep the code simple and well-commented. Open an issue before starting large changes.