unmask

install

Pick your OS × HTTP server to get the exact yum / apt / apk commands and web config snippet.

0 Choose a mode (speed-first or compatibility-first)

Clicking a card updates the dropdown below in sync.

nginx native module

  • Once a client has passed the challenge, it runs at the same speed as bare nginx (the server just verifies a short-lived pass cookie)
  • ~0.05 ms per request (cookie verification within the same process. No subrequest / no IPC)
  • ✓ rate_limit / honeypot paths are handled entirely within nginx
  • nginx only (not for Apache. For that, use forward-auth mode)
→ click to select "nginx · native module"

forward-auth mode

  • Works with nginx / Apache
  • Reuses the distro's stock HTTP server as-is (nginx stays untouched / Apache uses mod_lua)
  • ~0.5–2 ms per request (via subrequest)
→ click to select "nginx · forward-auth" (switch to Apache via the dropdown)

1 Choose your environment

Every option above is CI-verified. Derivative distros (openSUSE / Mint / Pop!_OS / etc.) and the full matrix live on the supported distro list.

2 Install the unmask core ?repo method: install unmask-release once first to set up the repo config + GPG public key → from then on you can install / upgrade the core with dnf install unmask / apt install unmask. Signature verification is handled automatically by the package manager.

Right after install: admin auto-starts via systemd (127.0.0.1:9477) and writes /etc/unmask/.setup-token. The setup wizard launches in section 4.

Upgrade: sudo dnf upgrade unmask && sudo systemctl restart unmask (on deb, sudo apt upgrade unmask).

sudo dnf install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm
sudo dnf install -y unmask
sudo yum install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm
sudo yum install -y unmask
sudo yum install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm
sudo yum install -y unmask
wget https://unmask.sh/dl/deb/unmask-release-latest.deb
sudo apt install -y ./unmask-release-latest.deb
sudo apt update && sudo apt install -y unmask
wget https://unmask.sh/dl/apk/unmask-release-latest.apk
sudo apk add --allow-untrusted ./unmask-release-latest.apk
sudo apk update && sudo apk add unmask

3 Install unmask-plugin-nginx (native plugin) ?Prerequisite: nginx must be installed (auto-pulled in step 4 via unmask-web-nginx dependencies).

postinstall: places load_module into the distro's conventional dir (/usr/share/nginx/modules/ or /etc/nginx/modules-enabled/) automatically. 14 nginx versions × OpenSSL 1.0 / 1.1 / 3 ABIs are bundled (1.10–1.30 series / glibc) so the matching module is picked at install time. No manual editing.

After an nginx upgrade: ABI mismatch surfaces as nginx -t errors → sudo dnf upgrade unmask-plugin-nginx (or sudo apt install --only-upgrade unmask-plugin-nginx on deb) realigns versions.

sudo dnf install -y unmask-plugin-nginx
sudo yum install -y unmask-plugin-nginx
sudo yum install -y unmask-plugin-nginx
sudo apt install -y unmask-plugin-nginx
sudo apk add unmask-plugin-nginx
CentOS 7 / RHEL 7: nginx is not in the OS stock (base) repo, so install it from EPEL first.
sudo yum install -y epel-release
sudo yum install -y nginx
CentOS 6 / RHEL 6: nginx is not in base, but EPEL has 1.10.3 (matches the oldest ABI bundled in the fat plugin). If you want something newer, the nginx.org HTTP repo (1.18.0) works too. Run either block below.
# A. via EPEL (nginx 1.10.3 / within the OS ecosystem. recommended)
sudo rpm -Uvh http://archive.kernel.org/centos-vault/6.10/extras/x86_64/Packages/epel-release-6-8.noarch.rpm
sudo yum install -y nginx
# B. nginx.org HTTP repo (nginx 1.18.0 / newer)
sudo tee /etc/yum.repos.d/nginx.repo <<'EOF'
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/6/x86_64/
gpgcheck=0
enabled=1
EOF
sudo yum install -y nginx

4 Install unmask-web-nginx (web config snippet) ?postinstall places /etc/unmask/forward-auth/{server,protect}.inc + the upstream symlink, validates with nginx -t, and prints the setup token / URL. It does not touch the running nginx — the wizard URL becomes reachable after the restart you run below.

The JA4 maps include is fully automatic: postinstall places a symlink at /etc/nginx/conf.d/00-unmask.conf/var/lib/unmask/nginx/http.inc. Since every distro's default include /etc/nginx/conf.d/*.conf; auto-loads it, you do not need to edit the http {} block.

sudo dnf install -y unmask-web-nginx
sudo yum install -y unmask-web-nginx
sudo yum install -y unmask-web-nginx
sudo apt install -y unmask-web-nginx
sudo apk add unmask-web-nginx
3 ways to open the setup wizard — pick the one that fits your environment and open /unmask/admin/setup/.
  1. [Recommended] Add one line to an existing vhost — open the setup wizard on the domain you already use. Inside the target server { } block, add the blue (highlighted) line:
    server {
        listen 443 ssl http2;
        server_name example.com;
        ssl_certificate ...;
    
        include /var/lib/unmask/nginx/server.inc;   # ← unmask itself (challenge serving, BAN, admin UI)
    
        root /var/www/example;
    }
    sudo nginx -t && sudo systemctl restart nginx (a restart — not reload — is what loads the freshly installed module) → open https://<your-domain>/unmask/admin/setup/.
  2. [Direct port]nothing to change if you can shell into the host itself: http://127.0.0.1:9477/unmask/admin/setup/ is reachable out of the box. Only when you want to open the wizard from a different host on the LAN (a dev PC etc.), change server.bind in /etc/unmask/config.yml to 0.0.0.0:9477sudo systemctl restart unmask → open http://<your-server>:9477/unmask/admin/setup/. Not recommended on public hosts (9477 in plaintext + setup token exposure risk).
  3. [SSH tunnel] — a universal fallback that needs no firewall changes.
    ssh -L 9477:127.0.0.1:9477 your-server -N &
    → open http://localhost:9477/unmask/admin/setup/.

Once inside the setup wizard, in order:

  1. Paste the setup token — shown on screen after package install. Or sudo cat /etc/unmask/.setup-token.
  2. Create the admin account for the admin UI — username + password. You'll log in to the admin UI with this account from now on.
  3. Configure the DB — the SQLite default is enough. Switch to MariaDB if needed.
After completion you're redirected to the admin home automatically. Remaining settings live under /unmask/admin/settings/.

5 Fire the challenge in the server {} you want to protect (ON/OFF switch) ?These highlighted lines are unmask's ON/OFF switch. Add them and bot protection fires; remove them and blocking / challenges stop immediately, with no effect on user traffic. The access-log statistics feed keeps flowing either way, so a safe rollout is step-by-step: monitoring (stats only) → trial (fire on one location) → production (fire site-wide).

What you actually write is 2 lines: include /var/lib/unmask/nginx/server.inc; directly inside server { }, and include /var/lib/unmask/nginx/protect.inc; wherever you want protection. Everything else (rate-limit zones, challenge dispatch, JA4 maps) is rendered by the admin into /var/lib/unmask/nginx/ and auto-loaded via the conf.d symlink placed in section 4.

Rate / burst / target paths live on the Settings → rate-limit tab. To restrict which domain can reach the admin UI, use Settings → Network → admin_allowed_hosts.

scope:

In the target server {} block (the .conf of your existing nginx vhost), add the blue (highlighted) lines. Everything else is your existing config — a reverse proxy is shown, but a static site (root + try_files) adds the same lines.

No need to edit existing locations — just add this one block.

server {
    listen 443 ssl http2;
    server_name example.com;
    ssl_certificate ...;

    include /var/lib/unmask/nginx/server.inc;   # ← unmask itself (challenge serving, BAN, admin UI)
    include /var/lib/unmask/nginx/protect.inc;  # ← outside any location (protects every location)

    location / {                              # ← your existing location stays unedited
        proxy_pass http://your-upstream;
    }
}

Once you're done editing, apply the config. If you have not restarted nginx since installing the plugin, restart (a reload does not load a new module); afterwards plain reloads are enough:

sudo nginx -t && sudo systemctl restart nginx

2 Install the unmask core ?repo method: install unmask-release once first to set up the repo config + GPG public key → from then on you can install / upgrade the core with dnf install unmask / apt install unmask. Signature verification is handled automatically by the package manager.

sudo dnf install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm
sudo dnf install -y unmask
sudo yum install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm
sudo yum install -y unmask
sudo yum install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm
sudo yum install -y unmask
wget https://unmask.sh/dl/deb/unmask-release-latest.deb
sudo apt install -y ./unmask-release-latest.deb
sudo apt update && sudo apt install -y unmask
wget https://unmask.sh/dl/apk/unmask-release-latest.apk
sudo apk add --allow-untrusted ./unmask-release-latest.apk
sudo apk update && sudo apk add unmask

3 Install unmask-web-nginx (web config snippet) ?Prerequisite: nginx must already be installed (if not, dependency resolution installs it automatically). For forward-auth mode the distro's bundled nginx is fine.

postinstall: places /etc/unmask/forward-auth/{server,protect}.inc + the upstream symlink, validates with nginx -t, and prints the setup token / URL. It does not touch the running nginx — the wizard URL becomes reachable after the reload you run below.

sudo dnf install -y unmask-web-nginx
sudo yum install -y unmask-web-nginx
sudo yum install -y unmask-web-nginx
sudo apt install -y unmask-web-nginx
sudo apk add unmask-web-nginx
3 ways to open the setup wizard — pick the one that fits your environment and open /unmask/admin/setup/.
  1. [Recommended] Add one line to an existing vhost — open the setup wizard on the domain you already use. Inside the target server { } block, add the single line
    include /etc/unmask/forward-auth/server.inc;
    sudo nginx -t && sudo nginx -s reload → open https://<your-domain>/unmask/admin/setup/.
  2. [Direct port]nothing to change if you can shell into the host itself: http://127.0.0.1:9477/unmask/admin/setup/ is reachable out of the box. Only when you want to open the wizard from a different host on the LAN (a dev PC etc.), change server.bind in /etc/unmask/config.yml to 0.0.0.0:9477sudo systemctl restart unmask → open http://<your-server>:9477/unmask/admin/setup/. Not recommended on public hosts (9477 in plaintext + setup token exposure risk).
  3. [SSH tunnel] — a universal fallback that needs no firewall changes.
    ssh -L 9477:127.0.0.1:9477 your-server -N &
    → open http://localhost:9477/unmask/admin/setup/.
Once inside the setup wizard, in order:
  1. Paste the setup token — shown on the install screen, or via sudo cat /etc/unmask/.setup-token.
  2. Create the admin account for the admin UI — username + password. You'll log in to the admin UI with this account from now on.
  3. Configure the DB — the SQLite default is enough. Switch to MariaDB if needed.
After completion you're redirected to the admin home automatically.

4 Enable forward-auth in the server {} you want to protect (ON/OFF switch) ?This single block is unmask's ON/OFF switch. Add it and bot protection runs; remove it and blocking / challenges stop immediately.

Dashboard aggregation path: in forward-auth mode, only requests for which the /_unmask/check subrequest was called are counted in the kind/cnt aggregation (the cookie-pass chart). The syslog path used by nginx-native mode (counts every request) is not used here, so requests outside protected locations don't show up in the dashboard. If you want every request counted, choose nginx-native mode.

scope:

In the target server {} block (the .conf of your existing nginx vhost), add the blue (highlighted) lines. Everything else is your existing config — a reverse proxy is shown, but a static site (root + try_files) adds the same lines.

No need to edit existing locations — just add this one block.

server {
    listen 443 ssl http2;
    server_name example.com;
    ssl_certificate ...;

    include /etc/unmask/forward-auth/server.inc;   # ← unmask itself (challenge serving, BAN, admin UI)
    include /etc/unmask/forward-auth/protect.inc;  # ← outside any location (protects every location)

    location / {                              # ← your existing location stays unedited
        proxy_pass http://your-upstream;
    }
}
Behaviour while the daemon is down (default: full fail-open) — forward-auth fails open completely by default when the daemon stops. Protected paths pass through the gate (auth_request) and mid-challenge visitors flow through to your backend too, so the site stays up (same as native mode). No extra config needed. To fail closed instead (return 503 so an upstream LB drains this node), see the FAQ.

Once you're done editing, reload nginx to apply the config:

sudo nginx -t && sudo systemctl reload nginx

2 Install the unmask core ?repo method: install unmask-release once first to set up the repo config + GPG public key → from then on you can install / upgrade the core with dnf install unmask / apt install unmask. Signature verification is handled automatically by the package manager.

sudo dnf install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm
sudo dnf install -y unmask
sudo yum install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm
sudo yum install -y unmask
sudo yum install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm
sudo yum install -y unmask
wget https://unmask.sh/dl/deb/unmask-release-latest.deb
sudo apt install -y ./unmask-release-latest.deb
sudo apt update && sudo apt install -y unmask

3 Install unmask-web-apache (auto-places the snippet) ?Prerequisite: Apache must already be installed (if not, dependency resolution also auto-installs httpd + mod_lua + mod_proxy_http).

postinstall: places /etc/httpd/conf.d/unmask-web.conf + /etc/httpd/unmask.lua, runs apachectl graceful, and prints the setup token / URL. The conf.d snippet sets up the /unmask/* proxy (makes https://<host>/unmask/admin/setup/ reachable from every VirtualHost) but does NOT auto-protect any VirtualHost — you opt in per-VirtualHost by adding the LuaHookAccessChecker line in section 4 (consistent with nginx mode's per-server include protect.inc;).

sudo dnf install -y unmask-web-apache
sudo yum install -y unmask-web-apache
sudo yum install -y unmask-web-apache
sudo apt install -y unmask-web-apache
Initialize with the setup wizard: after the package install, open https://<your-vhost>/unmask/admin/setup/ and follow the steps in order (with Apache, the conf.d snippet makes /unmask/* reachable from every VirtualHost).
  1. Paste the setup token — shown on screen after package install. Or sudo cat /etc/unmask/.setup-token.
  2. Create the admin account for the admin UI — username + password.
  3. Configure the DB — the SQLite default is enough. Switch to MariaDB if needed.
After completion you're redirected to the admin home automatically.

4 Enable forward-auth in the VirtualHost you want to protect (ON/OFF switch) ?This single block is unmask's ON/OFF switch. Add it and bot protection runs; remove it and blocking / challenges stop immediately.

Dashboard aggregation path: unlike nginx-native (plugin + unix datagram socket access_log), in Apache mode the kind/cnt aggregation (the cookie-pass chart) is incremented by 1 each time a request reaches admin's /_unmask/check via the forward-auth subrequest. In other words only requests for protected VirtualHosts are aggregated — it's not an "all requests counted" model like nginx-native.

fail-open: /etc/httpd/unmask.lua returns OK when the subrequest to admin fails (the request flows through to the backend / DocumentRoot).

<VirtualHost *:443>
    ServerName example.com
    SSLEngine on
    SSLCertificateFile ...

    LuaHookAccessChecker /etc/httpd/unmask.lua handle_request

    ProxyPass        / http://your-upstream/
    ProxyPassReverse / http://your-upstream/
</VirtualHost>
sudo apachectl configtest && sudo systemctl reload httpd
sudo apachectl configtest && sudo systemctl reload apache2

5 Verify + troubleshoot

5.1 Force-fire the challenge
# plain curl — no browser TLS, no JS. It should be served the challenge
curl -sk https://example.com/ | grep -oE 'unmask|challenge' | head -1
# → if "unmask" appears, the challenge HTML is being served
5.2 Confirm events are flowing in on the dashboard

The Stats tab → the "Challenge funnel" card. Success if serve / load / pow / captcha are increasing.

resilience: if the unmask daemon stops, the site fails open — already-passed visitors keep flowing, and not-yet-passed visitors skip PoW / CAPTCHA and still get the page they asked for (both native and forward-auth modes). For a deliberate observe-only rollout, the admin's Operating mode tab has a full pass-through (monitoring) mode.
5.3 Deployments with TLS terminated at an upstream LB / CDN

When TLS is terminated at an LB / CDN, extract the real client's JA4 on the LB and forward it via an X-Client-JA4 header. Per-LB setup (GCP / Cloudflare / AWS) lives in Capturing JA4 from an LB (docs) ↗. Behavior when JA4 is empty: What JA4 does (docs) ↗.

5.4 Common snags
  • 403 forbidden appears → add your IP / CIDR to admin_allow_from in settings → Network tab
  • JA4 verdict is empty → in native mode, check the load_module config; in forward-auth mode, check 5.3 (the LB's X-Client-JA4 header forwarding)
  • module ABI mismatch on nginx -t → nginx was upgraded but the module didn't follow. Align the versions with sudo dnf upgrade unmask-plugin-nginx (on deb, apt install --only-upgrade unmask-plugin-nginx)
  • The challenge HTML doesn't show → check that unmask is listening on 127.0.0.1:9477 (systemctl status unmask)