/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:
- An unconditional Nginx guard — the vhost's
location ^~ /adminblock 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. - 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 thedisable_admin_dos_protectionopt-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):
location ^~ /admin {
if ($cache_uid = '') {
return 403;
}
if ( $is_bot ) {
return 444;
}
...
try_files $uri @drupal;
}
- Anonymous request (no authenticated-session cookie, so
$cache_uidis 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_bot → 444 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:
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=adminqueries); 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:
;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:
disable_admin_dos_protection = TRUE
Per platform (applies to every site on the platform), in
sites/all/modules/boa_platform_control.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:
# 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:
- Whole-site IP allow-list (
ip_access) — locks an entire site to a per-instance IP list, everything else403. - Login/admin IP allow-list (
user_admin_access) — keeps the public site open while limiting only/userand/adminto the list.
Related
- Security model — protection #11 (restricted admin access, uid=1).
- Whole-site IP allow-list (
ip_access) anduser_admin_access— the IP-based access controls. - Nginx internals — the vhost generator that emits
the
/adminblock. - Control files & INI — the
disable_admin_dos_protectionINI opt-out among the site/platform control variables. - Mailing policy — a sibling default restriction (mail) with a similar opt-out pattern.