wiki.getshifting.com

— Sjoerd Hooft's InFormation Technology —

User Tools

Site Tools


ddosbyaibots

Protect DokuWiki from AI Bots

Summary: This wiki page shows how I overcame a DDOS attack by AI Bots on this wiki.
Date: 18 April 2026

I host this wiki on AWS lightsail as described here. This is mostly a hobby project of mine, to help people figure out technical challenges, but it also has a more serious purpose. I use it to showcase my skills, professionalism and passion about the stuff I work with. Potential customers or employers can see with their own eyes what I'm working on or have been working on in the past. So keeping this wiki healthy is important for me. Even if it wouldn't be used, it still matters to me. So when I woke up one day and noticed the wiki to be down, I wasn't happy, especially when I found out it was caused by a relentless number of page and media requests by AI Bots. The attack started around the 7th of March, and I just finished up with all the actions I had to do to keep my wiki safe for now, so it took me over a month of free time. I spent about 32 hours on it. And I'm still busy, but now, again, on writing a wiki page, this page, to share my knowledge. I hope you enjoy it.

One final remark I wanted to make. Hosting a website with actual content is nearly impossible for someone not tech-savvy nowadays. AI bots will keep on going requesting resources, and it just won't stop.

DDOS Attack

So why am I calling what happened a DDOS attack? Well, as from wikipedia:

In computing, a denial-of-service attack (DoS attack /dɒs/ doss) is a cyberattack in which the perpetrator seeks to make a machine or network resource unavailable to its intended users by temporarily or indefinitely disrupting services of a host connected to a network.

Even though all the AI bots maybe didn't intend to make my site unavailable, it was what happened. That is also caused because, my site being a hobby project, I'm using AWS lightsail. On AWS lightsail, you do not get to used the full CPU's available in the instance. You kind of earn a set of CPU burst capacity, which you can use when your site is heavily used for a short amount of time. However, when you use more burst capacity then you earned, you get limited to the performance baseline (if interested, see here for more information). As all the AI bots just went on with requests, my site was limited to it's performance baseline, which was too little too serve all the AI bots and legitimate users. Meaning, when visiting my wiki, all you got was errors.
For me, this meant I had to put up a maintenance page, so I could earn some burst capacity again. In the mean time, this would hopely scare of the AI bots. Which it didn't, as you can see in this burst capacity graph. Once the attach started, the burst capacity would decline, and climb up again once I enabled the maintenance mode. Then every time I tried to enable the wiki, the load would grow again:

Burst capacity over the first few days


After a few days I realized I needed more then just maintenance mode. In the month after, I implemented a more advanced robots.txt, a simpler sitemap.txt for more control, I enabled a botchecker, ditched apache htaccess for normal apache config files to improve performance and started blocking access to visitors who wouldn't come over cloudfront.

Robots.txt

A robots.txt file is a file in the root of a website used to implement the Robots Exclusion Protocol. Unfortunately, this relies on voluntary compliance. So, as it does help against the bigger more reliable AI crawlers, it doesn't do anything against malicious bots. But it does help a bit. I started with a fairly simple robots.txt to just deny access to all, but as I do want my wiki to be crawled by google and such I eventually created this:

User-agent: *
Disallow: /wiki/templates/
Disallow: /wiki/playground/
Disallow: /*?*rev=
Disallow: /*?*do=login
Disallow: /*?*do=admin
Disallow: /*?*redirect=no
 
Sitemap: https://wiki.getshifting.com/sitemap.txt
 
User-agent: AdsBot-Google
User-agent: AdsBot-Google-Mobile
User-agent: AhrefsBot
User-agent: Baiduspider
User-agent: barkrowler
User-agent: BitSightBot
User-agent: BLEXBot
User-agent: Bytespider
User-agent: CCBot
User-agent: coccocbot-web
User-agent: DataForSeoBot
User-agent: dataprovider
User-agent: dotbot
User-agent: facebookcatalog
User-agent: Google-Extended
User-agent: GoogleOther
User-agent: GPTBot
User-agent: ChatGPT-User
User-agent: HaloBot
User-agent: IbouBot
User-agent: ImagesiftBot
User-agent: keys-so-bot
User-agent: LinkupBot
User-agent: MauiBot
User-agent: Mediapartners-Google
User-agent: meta-externalads
User-agent: meta-externalagent
User-agent: meta-webindexer
User-agent: MJ12bot
User-agent: MojeekBot
User-agent: netEstate NE Crawler
User-agent: PerplexityBot
User-agent: PetalBot
User-agent: rogerbot
User-agent: serpstatbot
User-agent: SitesOverPagesBot
User-agent: Sogou web spider
User-agent: TerraCotta
User-agent: TikTokSpider
User-agent: YandexAdNet
User-agent: YandexBlogs
User-agent: YandexBot
User-agent: YandexComBot
User-agent: YandexImageResizer
User-agent: YandexImages
User-agent: YandexMarket
User-agent: YandexMedia
User-agent: YandexOntoDB
User-agent: YandexSitelinks
User-agent: YandexSpravBot
User-agent: YandexVerticals
User-agent: YandexVertis
User-agent: YandexVideo
User-agent: YandexWebmaster
Disallow: /
 
User-agent: bingbot
Crawl-delay: 10

This denies access to all dynamically build pages that should not be part of a crawler. It also provides a sitemap to provide a simple list of all pages, instead of all media and again all the dynamic pages. Last, it tries to enforce a crawl-delay to the bingbot who should support that.
In case this ever changes, the current version can be seen online here

Sitemap.txt

As seen in the robots.txt, I also created a sitemap.txt. Before I relied on the built-in sitemap of dokuwiki. However, some bots don't really adhere to this, so I created a text based simple one. I also creates a script that updates it every time I add a page in the repository.

I also disabled the google sitemap creation in the dokuwiki settings.

You can check the sitemap online.

Disabled Dokuwiki Actions

To reduce the load, and also block actions that should not be done by Bots, Users or who ever, I disabled the following dokuwiki actions:

- Recent Changes - Register - Sitemap - Old revisions - Other actions: media

Replace htaccess with Conf Files

While testing solutions I often found myself struggling with the interaction between htaccess and Apache config files. Finally, in the documentation I found it is better to avoid using htaccess when you have access to the server config files.

So instead of using htaccess as configured here I moved the rules into a apache conf file:

# DokuWiki Rewrite Rules
 
# File protection (originally from .htaccess)
<Files ~ "^([\._]ht|README$|VERSION$|COPYING$)">
    <IfModule mod_authz_core.c>
        Require all denied
    </IfModule>
    <IfModule !mod_authz_core.c>
        Order allow,deny
        Deny from all
    </IfModule>
</Files>
 
# Don't allow access to git directories
<IfModule alias_module>
    RedirectMatch 404 /\.git
</IfModule>
 
# DokuWiki specific rewrite rules
RewriteRule ^_media/(.*)              lib/exe/fetch.php?media=$1  [QSA,L]
RewriteRule ^_detail/(.*)             lib/exe/detail.php?media=$1  [QSA,L]
RewriteRule ^_export/([^/]+)/(.*)     doku.php?do=export_$1&id=$2  [QSA,L]
RewriteRule ^$                        doku.php  [L]
 
# Handle non-existent files/directories
RewriteCond %{REQUEST_FILENAME}       !-f
RewriteCond %{REQUEST_FILENAME}       !-d
RewriteRule (.*)                      doku.php?id=$1  [QSA,L]
RewriteRule ^index.php$               doku.php

Finally, I also reverted the htaccess overrides in the Apache main config files I configured before and added the new conf file:

DocumentRoot "/opt/bitnami/apache/htdocs"
<Directory "/opt/bitnami/apache/htdocs">
    #
    # Possible values for the Options directive are "None", "All",
    # or any combination of:
    #   Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
    #
    # Note that "MultiViews" must be named *explicitly* --- "Options All"
    # doesn't give it to you.
    #
    # The Options directive is both complicated and important.  Please see
    # http://httpd.apache.org/docs/2.4/mod/core.html#options
    # for more information.
    #
    Options Indexes FollowSymLinks
 
    #
    # AllowOverride controls what directives may be placed in .htaccess files.
    # It can be "All", "None", or any combination of the keywords:
    #   AllowOverride FileInfo AuthConfig Limit
    #
    # AllowOverride None
    AllowOverride None
 
    # DokuWiki Configuration
    Include "/opt/bitnami/apache/conf/dokuwiki/dokuwiki.conf"
 
    #
    # Controls who can get stuff from this server.
    #
    Require all granted
</Directory>

Implement Botcheck

When searching for solutions I also found many more dokuwiki users (here and here) to experience the same troubles. And not only the users, but als dokuwiki itself. The last link is from the main dokuwiki creator/maintainer and he created a botcheck, available on github. I decided to implement the botcheck, but I ran into some issues. Because I'm hosted behind cloudfront, all (valid - more on this later) requests come from cloudfront. Which means that the good bots also didn't have access anymore. So I implemented this PR myself. This extends the lookup utility to also allow IP addresses from good bots in the {HTTP:X-Forwarded-For} header.

Use Botcheck to check valid requests

Once I had botcheck working I had the idea to use botcheck as well to check if a request was valid. Remember, I am hosting on AWS lightsail but behind a Cloudfront. I mostly use Cloudfront to handle the certificates for me. So when you go to wiki.getshifting.com, you first go to cloudfront, and cloudfront then redirects you to the origin lightsail instance. Now, somehow, many requests were trying to access the origin lightsail instance directly.

To prevent this, I made an additional change to the botcheck.conf:

<IfDefine BOTCHECK_DIR>
    RewriteEngine On
 
    RewriteMap lookup prg:${BOTCHECK_DIR}/lookup
 
    # CloudFront IP restriction - block requests not coming from CloudFront
    RewriteCond ${lookup:cloudfrontaddresses.net.list;%{REMOTE_ADDR}|NOT_FOUND} =NOT_FOUND
    RewriteRule .* - [F,L]

The rewrite rule returns a 403 forbidden response [F] and stops processing further rules [L]. This of course meant I also needed to create and add all Cloudfront IP addresses to the new file cloudfrontaddresses.net.list.

Other steps taken

To make the list complete, I've also taken the following steps:

  • Implemented the update-bot-addresses.sh script to run after an update as well as weekly. Note that the script needed a small change to work for the common crawler
  • I also implemented a script to automatically update the cloudfrontaddresses.net.list. This was created by copilot so I won't post it here.
  • I also created systemd timers to run both scripts at a scheduled time. Again created by copilot.
  • I created a pipeline to be able to manage all customizations from my repository. The pipeline can be checked in my public repo
  • I also updated the apache logging in httpd.conf to include the {HTTP:X-Forwarded-For} IP address:
    # LogFormat "{ \"time\":\"%{%Y-%m-%d}tT%{%T}t.%{msec_frac}tZ\", \"process\":\"%D\", \"filename\":\"%f\", \"remoteIP\":\"%a\", \"host\":\"%V\", \"request\":\"%U\",\"query\":\"%q\",\"method\":\"%m\", \"status\":\"%>s\", \"userAgent\":\"%{User-agent}i\",\"referer\":\"%{Referer}i\"}" cloudwatch
    LogFormat "{ \"time\":\"%{%Y-%m-%d}tT%{%T}t.%{msec_frac}tZ\", \"process\":\"%D\", \"filename\":\"%f\", \"remoteIP\":\"%a\", \"xForwardedForIP\":\"%{X-Forwarded-For}i\", \"host\":\"%V\", \"request\":\"%U\",\"query\":\"%q\",\"method\":\"%m\", \"status\":\"%>s\", \"userAgent\":\"%{User-agent}i\",\"referer\":\"%{Referer}i\"}" cloudwatch

This wiki has been made possible by:

ddosbyaibots.txt · Last modified: by 127.0.0.1