Skip to content

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.

The EasyWAF dashboard
The dashboard: requests over the last 24 hours split into passed, challenged and blocked, with a per-site breakdown of each site's mix.

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
sudo tee /etc/yum.repos.d/easywaf.repo >/dev/null <<'EOF'
[easywaf]
name=EasyWAF
baseurl=https://repo.easysys.io/easywaf/stable/redhat
enabled=1
gpgcheck=1
gpgkey=https://repo.easysys.io/easywaf/stable/redhat/key.gpg
EOF

sudo dnf install easywaf
sudo systemctl enable --now easywaf
sudo zypper addrepo -fg https://repo.easysys.io/easywaf/stable/redhat easywaf
sudo zypper 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:

sudo systemctl restart easywaf

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:

curl -i "http://your-site/?q=xp_cmdshell"

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.