Protecting a Web Site with Anubis and Apache

Protecting a Web Site with Anubis and Apache

July 23, 2025

In an earlier post, I described using Anubis to block AI scrapers from our library’s Koha installation. That worked well enough that I decided to try using Anubis to protect my non-CGI web site that has some static content and a few Web-based services.

The Problem

As a starting point, I used this article from the official documentation about using Anubis with Apache. The simple configuration described there worked well for my static content.

But things get complicated with the several web services I also use. Anubis presents a challenge that can only be solved by a client that can run Javascript, typically a modern browser. But some of the web services I use have non-browser clients, like Joplin and the Bitwarden desktop app. So the Apache configuration must be designed to prevent Anubis from acting as the middleman for these services.

Anubis Configuration

As I did with the Koha site, install Anubis from the ...amd64.deb file in the Assets section of the latest release here. Then as root, create the Anubis configuration as follows.

In directory /etc/anubis, create two files. The first is env, the systemd environment variable file. Note that we are telling Anubis to listen on port 8082, and to forward requests to the unsecured web site at port 8083; you may want to use different ports to avoid conflicts with any web services you might be running. You should also generate a different key by using openssl rand -hex 32.

/etc/anubis/env
BIND=localhost:8082
BIND_NETWORK=tcp
DIFFICULTY=4
POLICY_FNAME=/etc/anubis/botPolicies.yaml
TARGET=http://localhost:8083
#TARGET=https://www.example.com:8083
# random, openssl rand -hex 32
ED25519_PRIVATE_KEY_HEX=727f7501fdbbae467ee95131ca911bb1eedadf009e2886f781a07237c6d4779c

Then create the Anubis policies file botPolicies.yaml. Here we are telling Anubis to allow known good search engine bots and WebDAV requests, but challenge everything else:

/etc/anubis/botPolicies.yaml
bots:
  # Search engine crawlers to allow
  - import: (data)/crawlers/_allow-good.yaml
  # Allow common "keeping the internet working" routes (well-known, favicon, robots.txt)
  - import: (data)/common/keep-internet-working.yaml
  - name: allow-webdav
    action: ALLOW
    expression: 'path.startsWith("/webdav/")'
  - name: "everyone"
    user_agent_regex: "."
    action: "CHALLENGE"

To make it easier to start and stop Anubis, create a systemd service file for it:

/etc/systemd/system/anubis.service
[Unit]
Description="Anubis HTTP defense proxy"

[Service]
ExecStart=/usr/bin/anubis
Restart=always
RestartSec=30s
EnvironmentFile=/etc/anubis/env
LimitNOFILE=infinity
DynamicUser=yes
CacheDirectory=anubis/hks3
CacheDirectoryMode=0755
StateDirectory=anubis/hks3
StateDirectoryMode=0755
ReadWritePaths=/run

[Install]
WantedBy=multi-user.target

Tell systemd about the new Anubis service and start it:

systemctl daemon-reload
systemctl enable anubis
systemctl start anubis
systemctl status anubis

Simple Apache Configuration

The Apache configuration to protect static content is fairly simple. We have a public-facing HTTPS site that forwards requests to Anubis. Anubis sends requests that pass its challenge to a local, non-public HTTP site that does the actual serving of files.

Anubis Diagram

To implement this scheme, the following Apache configuration creates two sites:

  • the local, non-public HTTP site (VirtualHost localhost:8083) that serves files in /var/www/html
  • the public-facing HTTPS site (VirtualHost *:443) that forwards requests to Anubis
ℹ️
In the following examples, the site configuration file uses Let’s Encrypt certificates, hence the filename 000-default-le-ssl.conf. The actual filename may be different in your installation.

First, the following Apache modules must be enabled using a2enmod:

  • headers
  • proxy
  • proxy_http
  • proxy_uwsgi

Then add the following line to /etc/apache2/ports.conf:

Listen 8083

Finally, edit the site configuration file as follows:

/etc/apache2/sites-enabled/000-default-le-ssl.conf
# Local HTTP file server
<VirtualHost localhost:8083>
   ServerAdmin webmaster@localhost
   ServerName www.example.com
   DocumentRoot /var/www/html
   ErrorLog ${APACHE_LOG_DIR}/error.log
   CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

# HTTPS listener that forwards to Anubis
<VirtualHost *:443>
   ServerAdmin webmaster@localhost
   ServerName www.example.com
   DocumentRoot /var/www/html
   ErrorLog ${APACHE_LOG_DIR}/error.log
   CustomLog ${APACHE_LOG_DIR}/access.log combined
   SSLCertificateFile /etc/letsencrypt/live/www.example.com/fullchain.pem
   SSLCertificateKeyFile /etc/letsencrypt/live/www.example.com/privkey.pem
   Include /etc/letsencrypt/options-ssl-apache.conf

   # Anubis forwarding
   RequestHeader set "X-Real-Ip" expr=%{REMOTE_ADDR}
   RequestHeader set X-Forwarded-Proto "https"
   RequestHeader set "X-Http-Version" "%{SERVER_PROTOCOL}s"
   ProxyPass / http://localhost:8082/
   ProxyPassReverse / http://localhost:8082/
</VirtualHost>

Adding Web Services

Now the tricky part begins. I had the following web services running before Anubis, and had to figure out how to configure them after Anubis:

Web Services Not Protected by Anubis

Configurations for web services that must not be protected by Anubis are placed in /etc/apache2/sites-enabled/000-default-le-ssl.conf in the <VirtualHost *:443> section.

Radicale, VaultWarden, XBrowserSync, Fossil

The Radicale, Vaultwarden, XBrowserSync, and Fossil web services are used by non-browser apps, so they must not be protected by Anubis. This is fine, because they are password-protected. They are configured in Apache as reverse proxies. Place the following lines before the Anubis proxy lines:

   RewriteEngine On
   RewriteRule ^/radicale$ /radicale/ [R,L]
   <Location "/radicale/">
       ProxyPass        http://localhost:5232/ retry=0 upgrade=websocket
       ProxyPassReverse http://localhost:5232/
       RequestHeader    set X-Script-Name /radicale
   </Location>

   ProxyPass /vaultwarden/ http://127.0.0.1:8000/vaultwarden/ upgrade=websocket
   ProxyPass /xbs/ http://127.0.0.1:8090/ upgrade=websocket
   ProxyPass /fossil/ http://127.0.0.1:8080/ upgrade=websocket

Protecting Fossil without Anubis takes some special care. This is done via user permissions. In each of my Fossil repositories, I set the permissions for the “nobody” user to “clone” and nothing else. This prevents bots from running expensive crawls through the repository timeline and doc links, but it still allows Fossil users to clone a repository from the command line.

ℹ️
Since I wrote the above paragraph, I have put Fossil back under the protection of Anubis. This prevents cloning, but it does allow non-logged-in users to view the docs and timeline, and to download zip files of the repository. This was accomplished by setting the permissions for the nobody user to ghorjz. Since Fossil is now behind Anubis, bots should be prevented from doing what a nobody user can do.

WebDAV

The WebDAV service is used by Joplin, so it must also avoid being protected by by Anubis. But WebDAV is served by Apache itself, not by a separate service via a reverse proxy. In fact, WebDAV will fail if it is protected by Anubis . So we need to tell Apache to not forward requests to /webdav/* to Anubis. Do this by placing the following lines before the Anubis proxy lines. The ProxyPass line with the “!” parameter is the magic that prevents it from being included in the Anubis ProxyPass:

   Alias /webdav /var/www/webdav
   <Directory "/var/www/webdav">
       DAV On
       AuthType Basic
       AuthName "webdav"
       AuthUserFile /usr/local/apache/var/.htpasswd
       Require valid-user
   </Directory>
   ProxyPass "/webdav/" "!"

Here we are using basic authentication (password protection) for WebDAV. The password file (AuthUserFile) was created using:

htpasswd -c /usr/local/apache/var/.htpasswd username

You can use the litmus program to test the WebDAV service. On Debian/Ubuntu, install it using:

apt install litmus

Then use it to test your WebDAV service:

litmus https://www.example.com/webdav/ username password

Web Services Protected By Anubis

The configurations for web services that must be protected by Anubis are placed in /etc/apache2/sites-enabled/000-default-le-ssl.conf in the <VirtualHost localhost:8083> section, after the DocumentRoot line. These services include:

  • Password-protected directory
  • InfCloud
  • Static web content (/var/www/html)

Password-protected Directory

To password-protect a directory (/var/www/private in this example), place the following lines after the DocumentRoot line:

   Alias /private /var/www/private
   <Directory "/var/www/private">
       AuthType Basic
       AuthName "Restricted Content"
       AuthUserFile /usr/local/apache/var/.htpasswd
       Require valid-user
   </Directory>

As in the WebDAV example above, the password file (AuthUserFile) was created using:

htpasswd -c /usr/local/apache/var/.htpasswd username

InfCloud

To protect the InfCloud calendar/contact web app, place the following lines after the DocumentRoot line:

   Alias /infcloud /var/www/infcloud
   <Directory "/var/www/infcloud">
       AuthType Basic
       AuthName "InfCloud"
       AuthUserFile /usr/local/apache/var/.htpasswd
       Require valid-user
   </Directory>

Static Web Content

The following line in the <VirtualHost localhost:8083> section is all that is necessary to protect static web content:

   DocumentRoot /var/www/html

Restart Apache

After all of this editing of configuration files, test that the files have the correct syntax:

apachectl -t

Then restart Apache and check its status:

systemctl restart apache2
systemctl status apache2

Complete Apache Configuration

For reference, here is the complete Apache site configuration file, as described above:

/etc/apache2/sites-enabled/000-default-le-ssl.conf
<VirtualHost localhost:8083>
   ServerAdmin webmaster@localhost
   ServerName www.example.com
   DocumentRoot /var/www/html
   ErrorLog ${APACHE_LOG_DIR}/error.log
   CustomLog ${APACHE_LOG_DIR}/access.log combined

   Alias /private /var/www/private
   <Directory "/var/www/private">
       AuthType Basic
       AuthName "Restricted Content"
       AuthUserFile /usr/local/apache/var/.htpasswd
       Require valid-user
   </Directory>

   Alias /infcloud /var/www/infcloud
   <Directory "/var/www/infcloud">
       AuthType Basic
       AuthName "InfCloud"
       AuthUserFile /usr/local/apache/var/.htpasswd
       Require valid-user
   </Directory>
</VirtualHost>

# HTTPS listener that forwards to Anubis
<VirtualHost *:443>
   ServerAdmin webmaster@localhost
   ServerName www.example.com
   DocumentRoot /var/www/html
   ErrorLog ${APACHE_LOG_DIR}/error.log
   CustomLog ${APACHE_LOG_DIR}/access.log combined
   SSLCertificateFile /etc/letsencrypt/live/www.example.com/fullchain.pem
   SSLCertificateKeyFile /etc/letsencrypt/live/www.example.com/privkey.pem
   Include /etc/letsencrypt/options-ssl-apache.conf

   RewriteEngine On
   RewriteRule ^/radicale$ /radicale/ [R,L]
   <Location "/radicale/">
       ProxyPass        http://localhost:5232/ retry=0 upgrade=websocket
       ProxyPassReverse http://localhost:5232/
       RequestHeader    set X-Script-Name /radicale
   </Location>

   ProxyPass /vaultwarden/ http://127.0.0.1:8000/vaultwarden/ upgrade=websocket
   ProxyPass /xbs/ http://127.0.0.1:8090/ upgrade=websocket
   ProxyPass /fossil/ http://127.0.0.1:8080/ upgrade=websocket

   Alias /webdav /var/www/webdav
   <Directory "/var/www/webdav">
       DAV On
       AuthType Basic
       AuthName "webdav"
       AuthUserFile /usr/local/apache/var/.htpasswd
       Require valid-user
   </Directory>
   ProxyPass "/webdav/" "!"

   # Anubis forwarding
   RequestHeader set "X-Real-Ip" expr=%{REMOTE_ADDR}
   RequestHeader set X-Forwarded-Proto "https"
   RequestHeader set "X-Http-Version" "%{SERVER_PROTOCOL}s"
   ProxyPass / http://localhost:8082/
   ProxyPassReverse / http://localhost:8082/
</VirtualHost>