Basic Redirects in WordPress/PHP
In PHP code you can express whatever redirection logic you want without being confined to the limited features on nginx. For example there could be a file mu-plugins/redirects.php that would include:
<?php
// Redirect any requests for www.example.fi or example.fi to example.com/fi/
if ( isset($_SERVER['HTTP_HOST']) && strpos($_SERVER['HTTP_HOST'], 'esimerkki.fi') !== false ) {
header("Location: https://example.com/fi/", true, 301);
exit; // Stop WordPress execution immediately as redirect headers were emitted
}
Another more elaborate one example would be:
switch ($_SERVER['HTTP_HOST']) {
# Enforce no www
# Use 301 to make redirect permanent and cached
# Use 302 for temporary (non-cached) redirects
case "www.example.com":
header("Location: https://example.com/", true, 301);
break;
# Multiple extra domains to same canonical domain
# Note! Many plugins already do this automatically, e.g. Seravo Plugin or Polylang
case "example.org":
case "exmple.net":
case "example.info":
header("Location: https://example.com/", true, 301);
break; # Localized domain to subfolder case "example.fi": header("Location: https://example.com/fi/", true, 301); break;
# Localized domain to subfolder
case "example.de":
header("Location: https://example.com/de/", true, 301);
break;
default:
header("Location: https://www.example.com/en/", true, 301);
}
exit; // Stop WordPress execution immediately as redirect headers were emitted
Redirecting HTTP Requests With Old Domain to New Domain
Change all occurrences of example.com to example.net in WordPress settings and contents. Once the site settings and contents are in order, create /data/wordpress/htdocs/wp-content/mu-plugins/redirects.php:
<?php
// Check that HTTP_HOST is set (so code is not run on wp-cli invocations)
if ( isset($_SERVER['HTTP_HOST']) && isset($_SERVER['REQUEST_URI']) ) {
switch ( $_SERVER['HTTP_HOST'] ) {
# Redirect all traffic to the new domain. This custom redirect is needed on
# sites where the built-in WordPress canonical domain does not fully work,
# e.g. sites with WPML or other odd plugins.
#
# Use 301 to make redirect permanent and cached
# Use 302 for temporary (non-cached) redirects
case 'example.com':
case 'www.example.com':
header('Location: https://example.net' . $_SERVER['REQUEST_URI'], true, 301);
exit; // Stop WordPress execution immediately as redirect headers were emitted
}
}
Force Canonical Domain
Google and other search engines don’t like if the exact same content is served on multiple websites. If a website, say example.com, also has the domain example.net if should not serve the same content on both domains but instead choose which domain is the canonical domain and then redirect all alternative domains to it. The same applies for subdomains. Websites should choose if they are available under www.example.com or example.com and then redirect the other to the one.
Normally website developers don’t need to bother with this, since WordPress core automatically redirects visitors to the canonical domain based on the home setting.
Also any site with the Seravo Plugin will automatically enforce both the canonical domain and the use of HTTPS to ensure all visits are protected.
However, certain plugins (e.g. WPML) that mess up the WordPress Rewrite API settings might break this, and in those rare cases a developer might need to create a custom /data/wordpress/htdocs/wp-content/mu-plugins/redirect.php file with the following contents:
<?php
// Redirect any requests for www.example.com to example.com (non-www)
if ( isset($_SERVER['HTTP_HOST']) && isset($_SERVER['REQUEST_URI']) && $_SERVER['HTTP_HOST'] == 'www.example.com' ) {
header("Location: https://example.com/" . $_SERVER['REQUEST_URI'], true, 301);
exit; // Stop WordPress execution immediately as redirect headers were emitted
}
REMEMBER! Test the redirect with curl -IL -H Pragma:nocache <url> to ensure it works!
Understanding the Difference of get_home() and get_siteurl() in WordPress
The home_url() (wp_options home) would be where you have set your homepage by setting General > Settings "Site Address (URL)" field.
The site_url() (wp_options siteurl) will always be the location where you can reach the site by appending on /wp-admin on the end, while home_url() would not reliably be this location.
Example
If you have:
home = https://example.com/store
siteurl = https://example.com
It would follow that:
https://example.com/ redirects to https://example.com/store/
https://example.com/store/ can be found in all internal links at the site
https://example.com/wp-admin/ stays as the WordPress management address
In this setup one could for example have a WordPress with WooCommerce running at the /store endpoint while the root / could be used for something completely else, for example a static website.
Testing Redirects
Please use curl to test redirects. Using a browser for testing will not work as the browser in most cases will cache the first redirect and after that no changes will be visible when testing with a broser. Using curl with header Pragma:no-cache ensures there is no caching at all and it will print location: headers that show clearly what the redirect is.
Example:
curl -IL -H Pragma:no-cache www.example.org
HTTP/1.1 200 OK
X-Cache: BYPASS
Location: https://example.com/
Content-Length: 100
HTTP/1.1 200 OK
X-Cache: BYPASS
Content-Length: 1270
Reviewing WordPress Rewrite API Contents
If a site has many redirect plugins, language plugins and maybe even custom WP Rewrite API rules registered in the theme the overall situation can become complex. Easiest way to review all current rewrites is to use wp-cli:
wp rewrite
usage: wp rewrite flush [--hard]
or: wp rewrite list [--match=<url>] [--source=<source>] [--fields=<fields>] [--format=<format>]
or: wp rewrite structure <permastruct> [--category-base=<base>] [--tag-base=<base>] [--hard]
wp rewrite flush
Success: Rewrite rules flushed.
wp rewrite list
+----------------+-----------------------------+-------------------------+
| match | query | source |
+----------------+-----------------------------+-------------------------+
| sitemap\.xml$ | index.php?the_seo_framework | other |
| | _sitemap=xml | |
| sitemap\.xsl$ | index.php?the_seo_framework | other |
| | _sitemap=xsl | |
| ^wp-json/?$ | index.php?rest_route=/ | other |
| ^wp-json/(.*)? | index.php?rest_route=/ | other |
| | $matches[1] | |
| ^index.php/ | index.php?rest_route=/ | other |
| wp-json/?$ | | |
| ^index.php/ | index.php?rest_route=/ | other |
| wp-json/(.*)? | $matches[1] | |
| case/?$ | index.php?post_type=case | other |
| case/feed/ | index.php?post_type=case& | other |
| (feed|rdf|rss| | feed =$matches[1 | |
| rss2|atom)/?$ | | |
...
Relative URLs and Alternative wp-content Locations
WordPress allows to set an alternative location for the wp-content directory by defining the WP_CONTENT_URL constant. This can also be used to set relative urls simply by replacing the default https://example.com/wp-content with simply /wp-content. See the wp-config.php in-line comments in the Seravo WordPress project template for an example.
