Lock down a server and put a WAF in front of your app
From the first deploy,
erpc worker lets SSH in only from your address and drops every other inbound connection. A [waf] table puts a filtering proxy in front of your API.This page describes ERPC CLI
v0.1.0-rc.4, a pre-release.The firewall
A deploy sets the server's firewall before the app first starts:
- SSH only from
firewall.ssh_allowand the address the deploy comes from; - every other inbound connection dropped, except
run.portfromfirewall.app_allow; for an app behind a WAF, the WAF's port in its place, and only where the WAF on the server already runs the rules[waf]now makes (below).
Each change runs under a 120-second rollback timer and is kept only once a fresh connection gets in, so a mistaken change cannot lock you out.
The allow list outlives the address it was made from, though: if your home address changes, the way back in is ERPC support for an ERPC server, or the provider's console for a server of your own. Keep a second address you can reach the server from (an office, a VPN exit) in
firewall.ssh_allow before you trust a home one.If your address changes, add every fixed address your admins use:
bash
Put a WAF in front of your app
Add one table to
erpc.toml:toml
erpc worker deploy then runs an HTTP reverse proxy on the server, in a systemd unit of its own (erpc-waf-<app>-<env>): an unprivileged dynamic user with no key, no secret and nothing of the worker's, held to 1 GiB of memory. It listens on waf.port, reads each request before the app does, refuses what its rules name, and forwards the rest to the app on 127.0.0.1:<run.port>.-
The app stays on loopback. Its own port is never opened from outside.
erpc worker apiand the health check reach the app on loopback, past the WAF. -
Who may connect is still the firewall's list, and the WAF's port is the one place that list may say everyone:shWith nobody listed, the WAF listens and nobody is let in, and the deploy says so.
-
The port is opened once the WAF listens on it, never before. A deploy whose WAF does not come up fails and says why, with the app still running.
-
When you change
[waf], the port is not opened to anyone new until a deploy has put the new rules in place. -
Taking
[waf]out oferpc.tomland deploying removes the WAF from the server.
WAF settings
Only
port is required. Every key, with its default:toml
rate, max_connections_per_address, ban and new_connections are turned off with false (ban = false), and a key left out is its default. erpc worker check says what the [waf] table will do, and what is wrong with it, before any deploy.What the WAF does with no setting needed
- A request that does not read as one unambiguous HTTP/1 request gets 400, and so does a path that is not in normal form.
- A request to change protocol (
Upgrade) gets 501. - Every refusal counts towards the ban, and each refusal ends its connection.
- An address is an IPv4 address, or an IPv6
/64. - The WAF does not forward the headers of the client's connection to the proxy (
Connectionand the headers it names,Keep-Alive,Proxy-Authenticate,Proxy-Authorization,Proxy-Connection,TE,Trailer),Expect, headers whose name has an underscore, or the client's ownForwarded,X-Real-IPandX-Forwarded-Proto. It setsX-Real-IPto the address the connection came from and adds that address to the end ofX-Forwarded-For; only the last entry ofX-Forwarded-Foris the proxy's word.
See what the WAF does
A forwarded request leaves no log line, and no header or body is ever logged; a blocked request's query is, as far as the 200 bytes reach. What a client sends cannot write on your terminal: the WAF writes its journal as printable ASCII.
What the WAF does not do
- No TLS. It speaks plain HTTP/1.1 on its port.
- No HTTP/2: it answers 400 to it.
- No WebSocket, nor any other
Upgrade: 501. - No look inside bodies. A body is measured, never read. The probe list is short and a noise filter, not a guarantee: the app still checks its input.
- No authentication. Who may call the app is the app's to decide, and the firewall's list.
- One address is one client. Clients behind one NAT, proxy or CDN share a rate and a ban; a client with many addresses has as many rates.
- Bans and counts are forgotten when the WAF restarts.
- It remembers at most 8192 addresses and holds at most 1024 connections at once.
- It listens on IPv4 only, so an IPv6 entry in
app_allowreaches nothing yet. - A path is compared as the bytes it decodes to. An app that reads paths more loosely (ignoring case,
;parameters, decoding twice) can be reached under a namedeny_pathsdoes not cover.allow_pathshas no such gap: what is not listed is not forwarded. - A slow reader is served to its end. A client that keeps taking an answer, however slowly, is served to the end.
- A flood can cut off a large upload. The WAF runs on one thread, so a flood can cut off a large upload with 408. An app that takes large bodies raises
body_timeout_secstogether withmax_body_bytes.
The worker guide has every rule, including when a change to
app_allow is held until the next deploy.





