Custom rewrites & location blocks

BOA has no .htaccess layer — every per-site rewrite, redirect, or custom location block is delivered through an operator include file dropped into a fixed directory and pulled into the rendered vhost at a defined position. The include file is never touched by the Provision backend, so it survives every barracuda upgrade and every Verify.

The two include points

The runtime vhost (Inc/vhost_include.tpl.php) globs two operator filenames from the per-frontend post.d directory and includes each at a different depth:

Filename Included at Override depth
nginx_force_include.conf early in the vhost, before BOA's standard location set (vhost_include.tpl.php:323) high — can override cache-bypass and static-file handling
nginx_vhost_include.conf mid-body — after the early cache-bypass and static-file locations but before the private-download, CSS/JS, and PHP-handler locations (vhost_include.tpl.php:1025) standard — overrides rules "below" it (private-download, CSS/JS, the @cache/@drupal/PHP handlers), not the higher-level cache-bypass/static rules above it

Both are globbed with a trailing *, so a nginx_force_include.conf.foo suffix variant is also picked up. A sibling fpm_include* glob (vhost_include.tpl.php:328) is pulled in alongside nginx_force_include for per-site PHP-FPM version overrides.

Choose nginx_vhost_include.conf first; switch to nginx_force_include.conf only when the override does not take because a higher-priority BOA block is winning the match.

Drop directories

The include is read from $aegir_root/config/server_master/nginx/post.d/, where $aegir_root resolves per frontend:

Frontend post.d drop directory
Octopus Satellite (per instance) /data/disk/<USER>/config/server_master/nginx/post.d/
Master (host-level Aegir frontend) /var/aegir/config/server_master/nginx/post.d/

Both rows carry the server_master/nginx/post.d/ suffix — that is the only path the rendered vhost's include directive reads. A file dropped into /var/aegir/config/includes/ (a real directory that holds the ip_access/ generated includes) is never picked up as a custom rewrite; that directory is not on the post.d include path.

Drop the file, then re-emit the vhost (below). The file's content is copied by reference at include time, not merged into the vhost, so your edits persist verbatim across upgrades.

Scope of custom rules

A custom include applies to every site on the same frontend unless each block is scoped to a host. The canonical pattern wraps each interesting block in an if ($host ~* …) { … } or a server_name guard so it only fires for one domain. Without that guard the rule leaks to every vhost the include is pulled into.

Recipes

Map legacy content paths to multisite

location ~* ^.+\.(?:jpe?g|gif|png|ico|swf|pdf|ttf|html?)$ {
  access_log off;
  log_not_found off;
  expires 30d;
  rewrite ^/files/(.*)$     /sites/$server_name/files/$1 last;
  rewrite ^/images/(.*)$    /sites/$server_name/files/images/$1 last;
  rewrite ^/downloads/(.*)$ /sites/$server_name/files/downloads/$1 last;
  rewrite ^/download/(.*)$  /sites/$server_name/files/download/$1 last;
  rewrite ^/docs/(.*)$      /sites/$server_name/files/docs/$1 last;
  rewrite ^/documents/(.*)$ /sites/$server_name/files/documents/$1 last;
  rewrite ^/legacy/(.*)$    /sites/$server_name/files/legacy/$1 last;
  try_files $uri =404;
}

For migrations where legacy URLs (/files/foo.pdf) must land under Drupal's multisite-aware /sites/<domain>/files/.

Site-specific 301 with a parent literal location

The parent literal location ^~ /path stops Nginx from searching other regex-based locations, so the nested regex wins over BOA's own regex blocks:

location ^~ /some-literal-path/no-regex-here {
  location ~* ^/some-path/or-regex-here {
    if ($host ~* ^(www\.)?(domain\.com)$) {
      return 301 $scheme://$host/destination/url;
    }
    try_files $uri @cache;
  }
}

Without the parent literal block, the regex location loses to BOA's regex blocks.

Bulk 301 for legacy .php URIs

location ^~ /services {
  location ~* ^/services {
    rewrite ^/services/accounting\.php$ $scheme://$host/node/18 permanent;
    rewrite ^/services/assurance\.php$  $scheme://$host/node/11 permanent;
    rewrite ^/services/audit\.php$      $scheme://$host/node/11 permanent;
    rewrite ^/services/taxation\.php$   $scheme://$host/node/92 permanent;
    rewrite ^/services/wealth\.php$     $scheme://$host/node/15 permanent;
    rewrite ^/services\.php$            $scheme://$host/node/17 permanent;
    try_files $uri @cache;
  }
  try_files $uri @cache;
}

For sites migrated off Joomla / Drupal 6 / legacy WordPress that exposed .php URIs. Note that bare .php requests are also subject to the abuse-guard .php catch-all (see Edge policy); a matched literal location short-circuits before that guard.

Domain-specific single-URL redirect

location = /about_us.php {
  if ($host ~* ^(www\.)?(foo\.com)$) {
    return 301 $scheme://$host/node/19;
  }
  return 444;
}

location = /… is the most specific match — it short-circuits location matching entirely. return 444 (Nginx's "close without response") quietly drops the request for other hosts that hit the same URI.

Avoid 404s on sites/default/files/ legacy paths

For a site moved from sites/default/ to sites/<domain>/:

if ($main_site_name = '') {
  set $main_site_name "$server_name";
}

location ^~ /sites/default/files {
  location ~* ^/sites/default/files/imagecache {
    access_log off;
    log_not_found off;
    expires 30d;
    set $nocache_details "Skip";
    rewrite ^/sites/default/files/imagecache/(.*)$ /sites/$main_site_name/files/imagecache/$1 last;
    try_files $uri @drupal;
  }
  location ~* ^/sites/default/files/styles {
    access_log off;
    log_not_found off;
    expires 30d;
    set $nocache_details "Skip";
    rewrite ^/sites/default/files/styles/(.*)$ /sites/$main_site_name/files/styles/$1 last;
    try_files $uri @drupal;
  }
  location ~* ^/sites/default/files {
    access_log off;
    log_not_found off;
    expires 30d;
    rewrite ^/sites/default/files/(.*)$ /sites/$main_site_name/files/$1 last;
    try_files $uri =404;
  }
}

How the include interacts with the rendered vhost

Provision renders the vhost in two parts: the per-site vhost.tpl.php frames the server and SSL blocks, while Inc/vhost_include.tpl.php is rendered once per frontend as the shared nginx_vhost_common.conf (under config/includes/, pulled into every vhost) carrying the Drupal-specific locations, the cache and static-file handling, and the PHP-FPM upstream. The two include points land your file at one of the two well-defined positions inside that shared body — force_include before any location block at all, vhost_include mid-body: after the early cache-bypass and static-file locations but ahead of the private-download, CSS/JS, and PHP (@cache/@drupal/.php$) handlers.

Applying the change

A custom include takes effect only after the vhost is re-emitted. Re-emission runs from a Verify on the affected site (per-site change) or on the platform (change that applies to every site on the platform):

# As the Aegir user, via Drush:
drush @<site-alias> provision-verify

Verify rewrites the vhost to /var/aegir/config/server_master/nginx/vhost.d/<site> (Master) or the /data/disk/<USER>/…/vhost.d/<site> equivalent on an Octopus instance, re-pulls the post.d includes, runs configtest, and reloads Nginx.

Debugging an include that does not take

  1. Read the rendered vhost on disk — confirm the include directive in the emitted vhost.d/<site> references your post.d file.
  2. Check syntaxnginx -t reports errors from include files with the include path and line.
  3. Reload after a config-only editservice nginx reload. (Verify auto-reloads; a manual edit of the include does not until the next reload.)
  4. Rule out the cache — the Speed Booster cache may be serving the old response. Bypass with a .dev. host or wait for the cache TTL.
  5. Wrong include point — if a higher-priority BOA block is winning, move the rule from nginx_vhost_include.conf to nginx_force_include.conf.

Related

  • Config templates — the BOA-deployed templates and the Provision master http{} config your includes layer over.
  • Nginx debugging — diagnosing 502/504 and the redirect-to-install loop.
  • Edge policy — the guard chain (.php catch-all, secret-path deny) a literal-location include short-circuits past.
  • Reference appendix — consolidated path and variable tables.