On a WordPress site, redirects and URL rewrites can be implemented at several levels depending on your needs:
Server-level redirects (
nginx): The fastest option for single-page redirects and large redirect lists, as requests are redirected before PHP and WordPress load. See Can I Add Custom Configuration to the HTTP Server (nginx)?.Plugins (e.g., Redirection): A convenient dashboard-based solution for content editors. See WordPress Redirects – Redirection Plugin.
PHP-based redirects (
mu-plugins): The most flexible approach when redirects require programmatic logic, conditional checks, or domain-specific routing.
PHP Redirects (mu-plugins/redirects.php)
To execute programmatic redirects before your theme or standard plugins load, create the file /data/wordpress/htdocs/wp-content/mu-plugins/redirects.php. Must-use plugins (mu-plugins) run automatically on every page load.
Important: Always wrap PHP redirect logic in isset( $_SERVER['HTTP_HOST'] ) so the code does not execute during command-line wp-cli invocations. Use HTTP status code 301 for permanent redirects and 302 for temporary ones.
Example 1: Redirecting a Secondary Domain to a Subdirectory
Following WordPress best practices, include a direct-access guard (ABSPATH) and a plugin header block (Plugin Name) at the top of the file so it appears clearly under Plugins > Must-Use in the WordPress dashboard:
<?php
/**
* Plugin Name: Custom Redirects
* Description: Site-specific PHP redirects.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Ensure the request comes from a browser and not WP-CLI
if ( isset( $_SERVER['HTTP_HOST'] ) && strpos( $_SERVER['HTTP_HOST'], 'esimerkki.fi' ) !== false ) {
header( 'Location: https://example.com/fi/', true, 301 );
exit; // Stop execution immediately after sending headers
}
Example 2: Centralized Routing for Multiple Domains
Include the same file header and ABSPATH check from the previous example, followed by:
if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
switch ( $_SERVER['HTTP_HOST'] ) {
// Redirect www to the primary domain (preserving the request path)
case 'www.example.com':
header( 'Location: https://example.com' . $_SERVER['REQUEST_URI'], true, 301 );
exit;
// Redirect secondary domains to the primary domain
case 'example.org':
case 'example.net':
header( 'Location: https://example.com' . $_SERVER['REQUEST_URI'], true, 301 );
exit;
// Redirect country-specific domains to language subdirectories
case 'example.fi':
header( 'Location: https://example.com/fi/', true, 301 );
exit;
case 'example.de':
header( 'Location: https://example.com/de/', true, 301 );
exit;
}
}Note: mu-plugins are loaded before WordPress's wp_redirect() and wp_safe_redirect() functions are defined (pluggable.php), so early redirects must use PHP's native header() function followed by exit. If you trigger redirects later in a theme or plugin (such as on the template_redirect hook), use WordPress's wp_safe_redirect() function instead.
Enforcing a Canonical Domain
Search engines treat identical content accessible across multiple addresses (such as example.com, www.example.com, and example.net) as duplicate content. You should always designate a single primary domain (canonical domain) and redirect all secondary domains to it using 301 redirects.
In most cases, you do not need to configure this manually:
WordPress Core automatically redirects visitors to the primary site URL configured in your settings.
Seravo Plugin automatically enforces both the canonical domain and HTTPS across all sites hosted on Seravo.
Certain multilingual plugins (such as WPML or Polylang) override WordPress's default rewrite rules, which can prevent automatic canonical domain redirects from working. In those cases, you can enforce the redirect using the mu-plugins/redirects.php approach above.
Difference Between home_url() and site_url()
WordPress separates your site's public-facing address from the directory where WordPress core files reside:
home_url()(homein the database; Settings > General > Site Address (URL) in the dashboard): The public URL visitors type into their browser and the base URL used for internal links.site_url()(siteurlin the database; WordPress Address (URL) in the dashboard): The location of your WordPress core files and admin dashboard (/wp-admin/).
Subdirectory Installation Example
If your database options are configured as:
home = https://example.com/store
siteurl = https://example.com
Requests to
https://example.com/redirect tohttps://example.com/store/.All internal site links resolve under
https://example.com/store/....The WordPress admin dashboard remains accessible at
https://example.com/wp-admin/.
Testing Redirects with curl
Avoid testing redirects solely in a web browser, as browsers aggressively cache 301 redirects and may not reflect subsequent changes.
Instead, test redirects from the command line using curl with the -IL flags (follow redirects and print HTTP response headers only) along with the Pragma: no-cache header to bypass server caching:
curl -IL -H "Pragma: no-cache" http://www.example.org
Example output for a working 301 redirect:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
X-Cache: BYPASS
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
X-Cache: MISS
Inspecting WordPress Rewrite API Rules
If your site uses multiple plugins, custom post types, or custom WP Rewrite API rules registered in your theme, you can inspect and refresh active rewrite rules with WP-CLI:
# List all registered rewrite rules
wp rewrite list
# Flush rewrite rules (equivalent to saving Settings > Permalinks)
wp rewrite flush
Relative URLs and WP_CONTENT_URL
You can configure a custom location for your wp-content directory or use relative paths by defining the WP_CONTENT_URL constant in wp-config.php:
define( 'WP_CONTENT_URL', '/wp-content' );
See the Seravo WordPress project template wp-config.php for a complete example.
Common Issues and Troubleshooting
Infinite redirect loop (
ERR_TOO_MANY_REDIRECTS): Verify thatnginxrules,mu-plugins/redirects.php, redirect plugins, and WordPress'shome/siteurlsettings are not redirecting in conflicting directions. When redirecting HTTP to HTTPS in PHP, inspect the$_SERVER['HTTP_X_FORWARDED_PROTO']header rather than relying solely on$_SERVER['HTTPS'].WP-CLI commands fail or print warnings: Ensure all custom redirects inside
mu-plugins/are wrapped in anif ( isset( $_SERVER['HTTP_HOST'] ) )check so they do not run during CLI commands.A new custom post type or page returns a
404error: The permalink rewrite rules are out of date. Flush the rules usingwp rewrite flushor by saving Settings > Permalinks in the dashboard.Updated redirects are not visible in the browser: Your browser has cached the previous
301response. Verify the live behavior withcurl -IL -H "Pragma: no-cache" <url>and clear the server cache withwp-purge-cacheif needed.
See Also
Properly configured redirects keep your site's URL structure clean, preserve search engine visibility during migrations, and prevent unnecessary server load. If you need assistance setting up complex redirect rules or troubleshooting redirect loops, contact our support team at [email protected].
