EasyWAF
A web application firewall that filters, monitors, and blocks malicious HTTP traffic.
EasyWAF is a high-performance HTTP reverse proxy that sits in front of your web applications. It inspects all incoming requests and blocks common attacks—like SQL injection, cross-site scripting (XSS), and automated crawlers—before they can touch your backend servers.
Traffic Flow
graph LR
Client["Client Request"] --> WAF["EasyWAF (Port 80/443)"]
WAF -->|SQLi, XSS, Rule Engine| Check{"Blocked?"}
Check -->|"Yes (Drop)"| Drop["HTTP 403 Forbidden"]
Check -->|"No (Forward)"| Backend["Backend App (Port 8080)"]
Features
- Real-time Request Filtering: Analyze headers, query parameters, cookies, and request bodies for attack signatures.
- Low Overhead: Built in Rust with asynchronous I/O to process thousands of requests per second with sub-millisecond latency.
- Dynamic Rule Reloads: Update your configurations and IP block lists on the fly without stopping or restart-dropping traffic.
- TLS Termination: Serve HTTPS for your sites with certificates managed from the web UI. Many sites share one HTTPS port, each presenting its own certificate, selected by SNI during the handshake. Plain HTTP and HTTPS can be served at once, with an optional redirect from one to the other.
- Signed Rule Updates: Corrected and new rule sets are published to a signed channel. EasyWAF tells you which policies are behind; applying is always a decision you make, never something that happens to a running appliance.
- Detailed Analytics Ingest: Seamlessly hooks into EasyLog to log blocked attacks and generate security metrics.
Installation
EasyWAF ships as a single binary and installs a service that starts on boot. Install it from the EasySYS package repository so upgrades come through your package manager. Packages are published for x86_64 and arm64; your package manager picks the right one.
# Add the EasyWAF repository (signed)
curl -fsSL https://repo.easysys.io/easywaf/stable/debian/key.gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/easysys.gpg
echo "deb [signed-by=/usr/share/keyrings/easysys.gpg] https://repo.easysys.io/easywaf/stable/debian ./" \
| sudo tee /etc/apt/sources.list.d/easywaf.list
sudo apt update
sudo apt install easywaf
sudo systemctl enable --now easywaf
For air-gapped hosts, grab the .deb or .rpm for your architecture from the
releases page:
sudo dpkg -i easywaf_*_amd64.deb # or _arm64.deb
sudo rpm -i easywaf-*.x86_64.rpm # or .aarch64.rpm
sudo systemctl enable --now easywaf
Upgrades then mean downloading the next package by hand — the repository is the easier path where the host has network access.
The package installs the binary to /usr/bin/easywaf and its runtime files —
templates, static assets, the bundled rule sets and config.toml — under
/opt/easywaf, plus a systemd unit. The database is created at
/opt/easywaf/easywaf.db on first start. The service runs as root so a site can
bind a privileged port such as 80.
Then open https://<host>:8443/. There is no account yet, so EasyWAF asks you to
create one — pick a username and password there. Plain HTTP on port 8080
redirects to the TLS port, so the password is never typed over a cleartext
connection.
Your browser will warn about the certificate. On first start EasyWAF generates a
self-signed one named easywaf and stores it under Certificates — it is
there so the management interface is never served over plain HTTP, not because
it is trustworthy. Replace it with your own certificate to clear the warning.
There is no password recovery
EasyWAF has no mailer and no second account to reset from, so the password you set here cannot be recovered — keep it somewhere safe. Changing it later is under Account → Change Password.
Keeping ports 8443 and 8080 off the public internet is still worth doing — bind them to a management network or firewall them — but it is defence in depth rather than the only thing protecting the appliance.
Upgrading from 0.4.0 or 0.4.1 — check your admin password
Those versions seeded an admin / admin account, and the upgrade does not
touch existing accounts. If you never changed it, change it now under
Account → Change Password; until you do, EasyWAF says so on every start
in the log and on the account page. Installations from 0.4.2 onwards have no
default account at all.
Upgrading from 0.3.x — the GUI has moved
The management interface is now on 8443 over HTTPS; 8080 does nothing but redirect there. Earlier documentation told you to firewall port 8080 to keep the GUI private. If you did that and opened nothing else, open 8443 to the same callers before you upgrade — otherwise 8080 will redirect you to a port your own firewall is blocking, and you will be locked out of your own GUI.
Configuration
EasyWAF reads /opt/easywaf/config.toml. The path is resolved relative to
the service's working directory, so keep the file where the package put it.
[proxy]
gui_port = 8080 # management GUI: plain HTTP, redirects to gui_tls_port
gui_tls_port = 8443 # management GUI: served here, over TLS
Nothing security-sensitive is configured here. As of 0.4.2 the key that signs session and CAPTCHA clearance cookies is generated on first run and kept in the database, so no installation can be left using a default value, and the administrator account is created through the GUI at first start.
The database defaults to easywaf.db in the service's working directory. Set the
DATABASE_URL environment variable to put it elsewhere — an environment variable
rather than a setting because the case that needs it is a container, where the
config file is baked into the image and the database has to sit on a mounted
volume to survive at all.
Restart to apply any edit:
geoip_db optionally points at a MaxMind-format .mmdb to use instead of the
bundled DB-IP Lite country database — a fresher DB-IP file, or MaxMind GeoLite2.
Leave it empty for the bundled one.
The shipped file also carries http_port and acme_webroot. These are
placeholders for features that aren't wired up yet and are ignored — in
particular, the ports EasyWAF listens on come from the sites you define, not
from http_port.
Sites, policies and rules are not configured here
Virtual hosts, their listening ports, WAF policies and rules all live in the database and are managed from the web UI, so adding a site never means editing a file and reloading.
Getting Started
1. Add a site
In the web UI open Sites → Add site and give it:
- a server name — the
Host:header EasyWAF routes on, e.g.example.com - a target — the upstream to forward to, e.g.
http://127.0.0.1:3000 - a listen port — the port EasyWAF accepts that site's traffic on
The listener is bound as soon as you save; adding a site on a new port does not
need a restart. Requests whose Host: matches no enabled site get a 404.
To serve the site over HTTPS, add a TLS port and pick a certificate (upload one under Certificates first). The plain HTTP port keeps serving unless you also turn on the site's HTTP-to-HTTPS redirect, so enabling HTTPS never silently takes the insecure port away. A site given a TLS port but no certificate does not bind it at all — a port that is not listening is far easier to diagnose than one that fails every handshake.
At this point the site is already being proxied. A site with no WAF policy
attached is simply a reverse proxy — traffic is forwarded and logged, and the
per-site security headers (HSTS, X-Frame-Options, …) still apply.
2. Attach a WAF policy
Under Policies, create a policy, pick the rule sets it should load, and set its rule engine mode:
| Mode | Behaviour |
|---|---|
Off |
No inspection at all. |
DetectionOnly |
Rules run and matches are recorded, nothing is blocked. Default for a new policy. |
On |
Enforced — matching traffic is blocked or challenged. |
Each matching rule adds its score; when the total reaches the policy's score threshold (10 by default) the request is blocked with 403. Rules marked as instant-block fire on their own regardless of score. If a challenge threshold is set, scores that reach it but stay under the block threshold get a CAPTCHA instead of a hard block.
Then assign the policy to the site under Sites → Settings.
3. Verify traffic protection
With the policy set to On, send a request carrying a signature that is an
instant block:
Expected response:
HTTP/1.1 403 Forbidden
content-type: text/plain; charset=utf-8
WAF block rule matched: SQLi: xp_cmdshell (MSSQL)
In DetectionOnly the same request is forwarded to your backend as usual and
only shows up under Traffic — which is the safe way to try a new policy
against real traffic before enforcing it.
4. Keeping rule sets up to date
Rule sets are versioned and published to a signed channel. EasyWAF checks it on start and every six hours, and the Policy Manager shows which policies are behind — per policy, because a set is imported into a policy: one may hold version 2 while another already has 3.
Nothing is applied on its own. Applying is a button, because a bad rule applied automatically is an outage across every site using that policy. When you apply one:
- The manifest's signature is checked before anything is read from it, and the set is checked against the SHA-256 that signed manifest gives for it. A channel serving something else is refused, and nothing reaches the database.
- Rules already installed are overwritten in place, keeping each rule's enabled state. A rule you disabled because it blocked your traffic stays disabled through the update.
- Rules you cloned are untouched, and a rule dropped from the set is left rather than deleted.
Imported rules cannot be edited — an update would overwrite the change without saying so. To customise one, use Clone: you get an ordinary custom rule that updates never touch, and its page tells you when the set it was forked from has moved on. The original stays enabled unless you disable it, so a clone adds to what the policy enforces rather than replacing it.
Under Settings → Rule Updates you can turn the check off — an appliance with no outbound access should not keep trying — or point it at a different channel. Pointing it elsewhere does not relax anything: the signature and hash checks are the same wherever the manifest came from, and the trusted key ships with EasyWAF.