HOW TO: Serve wp-admin from a separate subdomain (or domain)

October 22, 2019

NOTICE: Don't do this unless your situation really REALLY calls for it - or know exactly what you're doing. There are a great deal of implications for this kind of setup that might leave you in a bad way down the line.

There are a few reasons why you might want to use a separate domain for the admin area of your WordPress site. It's generally a terrible unless you really need to.

This guide isn't a recommendation - rather it's a guide for folks who are in a unique and tricky situation where they need different sets of the DNS-level processing for admin than they do for the frontend (ie. reverse proxying breaks Rest API requests, Cloudflare/GES or Sucuri is too strict on admin processes, etc.).

There are a number of ways of doing this. I'm going to share the most succinct and straight-forward way I've found. Tweak as needed, of course.

For the purposes of this guide, I'll be setting my main, public facing domain as 403page.com and my domain for admin as admin.403page.com.

Step 1: Modify your wp-config.php

We need WordPress to allow more than one domain to connect to the same site. By default, the home and siteurl defines in wp-config.php or in the database will always be redirected to - no matter what other domain is making the request. So we need to override that WordPress functionality and let your site know that it will respect multiple domains - in this case 403page.com and admin.403page.com.

With just the home and siteurl overrides, uploading something like image.png will be stored as https://admin.403page.com/wp-content/uploads/2019/10/image.png.

We don't want that. Rather we want it to be stored as https://403.ie/wp-content/uploads/2019/10/image.png.

To fix this we need to hardcode the wp-content path to always be the root domain.

Here are the rules to add to wp-config.php:

define ('WP_SITEURL', 'https://' . $_SERVER['HTTP_HOST']);
define ('WP_HOME', 'https://' . $_SERVER['HTTP_HOST']);
define ('ADMIN_SITEURL', 'https://admin.403page.com');
define ('ADMIN_COOKIE_PATH', FALSE);
define ('COOKIE_DOMAIN', 'admin.403page.com');
define ('WP_CONTENT_URL', "https://403.ie/wp-content");

PRO TIP: Using $_SERVER['HTTP_HOST'] may cause WP-CLI to throw errors when running commands, because the variable is not set during it's runtime. It looks like this:

PHP Notice:  Undefined index: HTTP_HOST in phar:///usr/local/bin/wp/vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php...

It doesn't actually break anything - it's just annoying. You can solve it with a handy condition like this before the above defines (just make sure to change YOURDOMAIN.COM:

if (defined('WP_CLI') && WP_CLI) {
  $_SERVER['HTTP_HOST'] = $_SERVER['SERVER_NAME'] = 'YOURDOMAIN.COM'; }

So the full snippet looks like:

if (defined('WP_CLI') && WP_CLI) {
  $_SERVER['HTTP_HOST'] = $_SERVER['SERVER_NAME'] = 'YOURDOMAIN.COM'; }
define ('WP_SITEURL', 'https://' . $_SERVER['HTTP_HOST']);
define ('WP_HOME', 'https://' . $_SERVER['HTTP_HOST']);
define ('ADMIN_SITEURL', 'https://admin.403page.com');
define ('ADMIN_COOKIE_PATH', FALSE);
define ('COOKIE_DOMAIN', 'admin.403page.com');
define ('WP_CONTENT_URL', "https://403.ie/wp-content");

Step 2 (optional): Modify your .htaccess, if you're on Apache

You may find certain requests are generating errors in your browser console output. For example, requests to/from admin.403page.com/wp-json might not be playing nice as the request is expected to be 403page.com/wp-json/.

Use the example below to create .htaccess rewrites that will fix these paths.

# Correct Guttenberg requests to use the public facing site
RewriteCond %{HTTP_HOST} ^admin.403page.com/wp-json/(.*)
RewriteRule ^$ https://403.ie/wp-json/$1 [L,R=301]

# Correct wp-content/plugins/* paths to use the public facing facing site
RewriteCond %{HTTP_HOST} ^admin.403page.com/wp-content/plugins(.*)
RewriteRule ^$ https://403.ie/wp-content/plugins/$1 [L,R=301]

CORS: Cross-origin Resource Sharing

Requests are still going to be made to the root domain from the subdomain. Even though we're technically dealing with the same site (403page.com and admin.403page.com), you may still run into issue with browsers having cross-origin sharing issues. Often you'll see javascript files and fonts failing because of this.

We just need to instruct the browser that it's ok to share resources between the domains with headers. Below are nginx and Apache versions of the header configurations, you'll likely just need one or the other.

Step 3a: CORS in nginx

Add this line to your nginx configuration to open up resource sharing completely in nginx:

add_header Access-Control-Allow-Origin *;

You may prefer just to specify specific static resources that are ok to share. In that case, you can use something like this:

location ~* \.(eot|font.css|otf|ttc|ttf|woff|woff2|js|png|jpg|jpeg|gif|webp)$ {
    add_header Access-Control-Allow-Origin *;
}

Step 3b: CORS in Apache

Alternatively, add this line to your .htaccess file in the root of your site to open up resource sharing in Apache.

<IfModule mod_headers.c>
Header add Access-Control-Allow-Origin "*"
</IfModule>

You may prefer just to specify specific static resources that are ok to share. In that case, you can use something like this:

<IfModule mod_headers.c>
    <FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff|js|png|jpg|jpeg|gif|webp)$">
        Header set Access-Control-Allow-Origin "*"
    </FilesMatch>
</IfModule>

Step 4 (bonus): Redirect wp-admin to the subdomain

In my particular setup, I added a nginx redirect so that when https://403.ie/wp-admin was requested, I'd automatically be redirected to https://admin.403page.com/wp-admin (and as a result, wp-login.php).

Here are the 2 nginx redirects I used:

403page.com/wp-admin/(.*) https://admin.403page.com/wp-admin/$1
403page.com/wp-login.php(.*) https://admin.403page.com/wp-login.php$1

That's it!