XML sitemaps are important for SEO, it provides a list of pages for search engines to crawl from your website. They can also be used to help inform about the structure of your content.
There are plugins out there such as Yoast SEO that can generate them automatically in WordPress but there might be certain cases where you need to build a custom sitemap. For example, you might have hosting restrictions, specific URL requirements, or using a reverse proxy.
Table of Contents
This guide explains how to create a custom XML sitemap in WordPress for a reverse proxy use case but can be adapted for other cases too…
Step 1: Intercept Sitemap Requests
Intercept requests for custom sitemap URLs (e.g., /custom-sitemap_index.xml
) and generate the required XML directly. Use the init
hook to catch the requests early.
add_action('init', function () {
if (isset($_SERVER['REQUEST_URI'])) {
$custom_sitemaps = [
'/custom-sitemap_index.xml' => 'index',
'/custom-post-sitemap.xml' => 'post',
'/custom-page-sitemap.xml' => 'page',
// Additional cases for categories and authors...
];
foreach ($custom_sitemaps as $path => $type) {
if (strpos($_SERVER['REQUEST_URI'], $path) === 0) {
if ($type === 'index') {
generate_custom_sitemap_index();
} else {
generate_custom_sitemap($type);
}
exit;
}
}
}
});
This ensures WordPress processes the correct XML before default templates or redirects interfere.
Step 2: Generate the Sitemap Index
The sitemap index acts as a directory for individual content sitemaps (e.g., posts, pages, categories).
function generate_custom_sitemap_index() {
$sitemaps = [
'custom-post-sitemap.xml' => $wpdb->get_var("SELECT MAX(post_modified_gmt) FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'post'"),
'custom-page-sitemap.xml' => $wpdb->get_var("SELECT MAX(post_modified_gmt) FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'page'"),
'custom-post-sitemap.xml' => '2025-01-31T15:44:00+00:00',
// Additional cases for categories and authors...
];
header('Content-Type: application/xml; charset=utf-8');
echo '<?xml version="1.0" encoding="UTF-8"?>';
echo '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
foreach ($sitemaps as $slug => $lastmod) {
$url = site_url($slug);
echo "<sitemap>";
echo "<loc>$url</loc>";
echo "<lastmod>$lastmod</lastmod>";
echo "</sitemap>";
}
echo '</sitemapindex>';
exit;
}
Step 3: Create Content-Specific Sitemaps
For each content type (e.g., posts, pages), generate a corresponding XML file. Replace the domain in URLs when needed, such as for reverse proxies.
function generate_custom_sitemap($type) {
global $wpdb;
header('Content-Type: application/xml; charset=utf-8');
echo '<?xml version="1.0" encoding="UTF-8"?>';
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
$site_url = get_site_url();
switch ($type) {
case 'post':
$posts = $wpdb->get_results("SELECT ID, post_modified_gmt FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'post'");
foreach ($posts as $post) {
$url = str_replace($site_url, 'https://proxy-domain.com', get_permalink($post->ID));
$lastmod = gmdate('Y-m-d\TH:i:s+00:00', strtotime($post->post_modified_gmt));
echo "<url><loc>$url</loc><lastmod>$lastmod</lastmod></url>";
}
break;
case 'page':
$pages = $wpdb->get_results("SELECT ID, post_modified_gmt FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'page'");
foreach ($pages as $page) {
$url = str_replace($site_url, 'https://proxy-domain.com', get_permalink($page->ID));
$lastmod = gmdate('Y-m-d\TH:i:s+00:00', strtotime($page->post_modified_gmt));
echo "<url><loc>$url</loc><lastmod>$lastmod</lastmod></url>";
}
break;
// Additional cases for categories and authors...
}
echo '</urlset>';
exit;
}
Step 4: Prevent Trailing Slash Issues
To avoid WordPress redirecting sitemap URLs to versions with trailing slashes, disable canonical redirects for sitemap paths.
add_filter('redirect_canonical', function ($redirect_url, $requested_url) {
$sitemap_paths = [
'custom-sitemap_index.xml',
'custom-post-sitemap.xml',
'custom-page-sitemap.xml',
// Additional cases for categories and authors...
];
foreach ($sitemap_paths as $path) {
if (strpos($requested_url, $path) !== false) {
return false; // Disable redirect
}
}
return $redirect_url;
}, 10, 2);
Step 5: Add Rewrite Rules
Register custom rewrite rules to make WordPress recognise sitemap URLs.
function add_custom_sitemap_rewrite_rules() {
add_rewrite_rule('^custom-sitemap_index\.xml$', 'index.php', 'top');
add_rewrite_rule('^custom-post-sitemap\.xml$', 'index.php', 'top');
add_rewrite_rule('^custom-page-sitemap\.xml$', 'index.php', 'top');
// Additional cases for categories and authors...
}
add_action('init', 'add_custom_sitemap_rewrite_rules');
Flush the rewrite rules after activation by visiting Settings > Permalinks and clicking Save Changes.
Testing
Once the plugin is activated, verify the following URLs:
/custom-sitemap_index.xml
/custom-post-sitemap.xml
/custom-page-sitemap.xml
Check the <loc>
tags in the XML files to ensure the URLs are correct, especially if a reverse proxy is in use.