Skip to content

Powered by Grav

`/admin*` URL protection

/admin* URL protection

By default, BOA guards Drupal's /admin* paths against anonymous access — both as anti-brute-force hardening and to prevent crawl-of-admin-pages noise. Two independent mechanisms are in play, and it matters which is which because only one of them has an opt-out:

  1. An unconditional Nginx guard — the vhost's location ^~ /admin block refuses an anonymous or bot request outright. It is emitted on every site and is not keyed on any INI variable, so it cannot be turned off per site.
  2. An INI-gated PHP 301 redirect-to-homepage — the BOA front-end include redirects anonymous requests for /admin* (and /logout, /privatemsg, /approve) to the site homepage. This is the layer the disable_admin_dos_protection opt-out controls.

This page covers both mechanisms and how to opt out of the redirect per site or per platform. For the separate IP-based access controls — locking a whole site, or just its login/admin surface, to a list of IP addresses — see whole-site ip_access and user_admin_access.

The unconditional Nginx guard

For every Drupal site, the BOA-emitted Nginx vhost contains a location ^~ /admin block that decides on the recovered client identity (vhost_include.tpl.php, the /admin block):

NGINX
location ^~ /admin {
  if ($cache_uid = '') {
    return 403;
  }
  if ( $is_bot ) {
    return 444;
  }
  ...
  try_files $uri @drupal;
}
  • Anonymous request (no authenticated-session cookie, so $cache_uid is empty) → 403.
  • Bot (matched by $is_bot) → 444 (connection closed, no response).
  • Authenticated user → passes through to Drupal (try_files $uri @drupal).

This block is unconditional — it is not tied to disable_admin_dos_protection or any other INI toggle, and there is no per-site way to switch it off.

It is one of several sibling per-path guards in the vhost (the redis/cache-backend/reports paths use the same $cache_uid = ''403 / $is_bot444 pattern). The disable_admin_dos_protection opt-out below does not relax this guard.

The INI-gated 301 redirect-to-homepage

Separately, BOA's front-end PHP include (global-front-end.inc, also mirrored in global.inc) redirects an anonymous visitor away from /admin* to the site homepage with an HTTP 301 before Drupal renders anything:

PHP
if ($is_anon == 'ANONYMOUS') {
  if (preg_match("/^\/(?:[a-z]{2}\/)?(?:admin|logout|privatemsg|approve)/", $_SERVER['REQUEST_URI'])) {
    if (empty($all_ini['disable_admin_dos_protection'])) {
      header("Location: " . $base_url . "/", true, 301);
      exit;
    }
  }
}

The match covers /admin*, /logout, /privatemsg and /approve (with an optional two-letter language prefix, e.g. /de/admin). This is current, live BOA behaviour, not a legacy Drupal artefact — it is the mechanism the disable_admin_dos_protection INI variable turns off.

Note the interaction with the Nginx guard above.

A request with a stale or expired session cookie still matches the SESS* cookie pattern that populates $cache_uid, so it is not empty and passes the Nginx 403 gate — but the session is invalid, so PHP classifies the visitor as ANONYMOUS and issues the 301 to the homepage.

That is why a user with a stale cookie can be "bounced to the homepage even though they were logged in": they clear Nginx but hit the PHP redirect.

Why it exists

  • Brute-force attack surface reduction — bots scanning for Drupal admin pages are refused before they can attempt logins.
  • Crawl-volume reduction — old Drupal versions exposed /admin* paths to anonymous users (e.g. ?q=admin queries); the Nginx block short-circuits that pattern.
  • Defence in depth — even if Drupal itself misbehaves and leaks admin-page content to anonymous users, Nginx refuses the request first.

When you'd opt out

Legitimate workflows that need /admin* reachable by anonymous clients:

  • Custom auth flows that depend on a specific admin URL being reachable pre-auth.
  • Drupal Webform / Form API integrations with URLs under /admin/... that anonymous clients must POST to.
  • Specific application requirements dictated by an upstream software vendor.

If you control the site and know what you are doing, you can opt out per site or per platform. Note the opt-out only lifts the PHP 301 redirect; a truly cookieless anonymous client is still refused by the unconditional Nginx guard, so these workflows must present a well-formed session cookie to reach /admin*.

Per-site / per-platform opt-out — INI

The control is the disable_admin_dos_protection INI variable, defined in both the site and platform INI templates (default.boa_site_control.ini and default.boa_platform_control.ini), shipped commented at its default of FALSE:

INI
;disable_admin_dos_protection = FALSE

To disable the /admin DoS-protection redirect, set it to TRUE (uncommented). This suppresses the 301 redirect-to-homepage described above so that an anonymous request for /admin* reaches Drupal instead of being bounced to the homepage. It does not relax the unconditional Nginx 403/444 guard — that block ignores this variable entirely (see the caveat under Verify below).

Per site, in <platform-root>/sites/<domain>/modules/boa_site_control.ini:

INI
disable_admin_dos_protection = TRUE

Per platform (applies to every site on the platform), in sites/all/modules/boa_platform_control.ini:

INI
disable_admin_dos_protection = TRUE

The value is read into the front-end include set (global-ini.inc defines its FALSE default; global-front-end.inc / global.inc consume it in the redirect condition) and is applied at the PHP layer on every request — it is not consumed by any night-maintenance script.

There is no admin_url_protection variable — that key does not exist anywhere in the codebase, so a line such as admin_url_protection = 0 is a no-op.

After saving, wait ~60 s (PHP-FPM opcache) and run Verify on the site.

Platform-level use case: a custom platform with a non-Drupal codebase behind Ægir for hosting that does not follow Drupal's URL conventions.

Verifying the protection state

Because two layers are involved, what curl reports depends on whether the request carries a session cookie:

SH
# Anonymous, no cookie at all — hits the unconditional Nginx guard first.
curl -I https://your-site.example.com/admin/
#   => 403 (or 444 for a recognised bot UA — connection closed).
# The disable_admin_dos_protection opt-out does NOT change this: the Nginx
# 403/444 guard is unconditional and always fires before Drupal.

# With a valid (or merely well-formed / stale) SESS* cookie — clears the Nginx
# guard because $cache_uid is then non-empty, and reaches the PHP layer:
#   - protection on (default):  301 redirect to the site homepage
#   - after opting out (TRUE):   the request reaches Drupal (e.g. admin login)

BOA also emits an X-Ini-Disable-Admin-Dos-Protection response header carrying the resolved value, which is a quick way to confirm the INI value that the front-end include actually loaded for the site.

Key point: setting disable_admin_dos_protection = TRUE opts out of the PHP 301 redirect only. It does not make a cookieless anonymous client reach Drupal's /admin* — the unconditional Nginx 403/444 guard still refuses that request first.

Stronger, IP-based controls

The protection above is path-scoped and keyed on session state. To restrict a site — or just its admin surface — to a fixed set of IP addresses instead, BOA ships two sibling generators:

© 2026 BOA Documentation. All rights reserved.