It's a very common requirement. You have an existing website, maybe built on .NET, Java or PHP, or running on IIS, and nobody wants to touch or rewrite it. At the same time, the marketing team wants an easy-to-use CMS like WordPress for the blog or careers section, on the same domain: yourdomain.com/blog, not a subdomain.
Running WordPress in a subfolder and routing traffic at the edge (with Cloudflare Workers, Nginx or a similar reverse proxy) is a great solution. But it's very easy to set it up in a way that works fine for a few weeks and then breaks in three different places at once.
This guide covers the one rule you must follow, the setup steps, and the checks to run before you hand the site to your content editors.
The golden rule
WordPress's folder path on the server must match its public URL path exactly.
If your public URL is yourdomain.com/blog, install WordPress inside a physical folder named blog on the WordPress server. Don't install it in the server's root folder and strip the /blog prefix at the edge.
Stripping the prefix looks like less work, but don't do it. WordPress builds URLs from two sources:
- Its own settings, which say
/blog. - The incoming request, which won't contain
/blogif the proxy stripped it.
When the two disagree, you get strange bugs: page-builder AJAX calls fail, canonical redirects drop the subfolder, and infinite redirect loops appear. Matching the folder name to the public URL avoids all of this from day one.
Step-by-step setup
1. Create the subfolder on the server first
Decide on the public path, for example /blog or /careers. On the server that hosts WordPress, create that exact folder and install WordPress inside it, not in the server root.
2. Set the WordPress and Site URLs
In wp-admin, go to Settings → General and set both WordPress Address (URL) and Site Address (URL) to the full public address:
https://yourdomain.com/blog
3. Keep WordPress's standard .htaccess
Because the folder matches the URL, WordPress's standard rewrite rules work as they are (on Apache):
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /blog/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /blog/index.php [L]
</IfModule>
# END WordPress
If Apache ever logs "Request exceeded the limit of 10 internal redirects", something upstream is still stripping the subfolder prefix.
4. At the edge, change only the hostname
The edge router should send /blog requests to the WordPress server with the path unchanged. It only swaps the destination host.
Cloudflare Worker:
const WORDPRESS_HOST = 'your-wp-server.example.com';
export default {
async fetch(request) {
const url = new URL(request.url);
// Match /blog and /blog/... but not /blogger or /blog-old
if (url.pathname === '/blog' || url.pathname.startsWith('/blog/')) {
const originalHost = url.hostname;
url.hostname = WORDPRESS_HOST; // the path stays as-is: no stripping
const proxied = new Request(url, request);
proxied.headers.set('X-Original-Host', originalHost);
proxied.headers.set('X-Original-Proto', 'https');
return fetch(proxied);
}
return fetch(request); // everything else goes to the main app
},
};
Nginx does the same thing. When proxy_pass has no path after the host, the original URI, including /blog/, is passed through unchanged:
location /blog/ {
proxy_pass https://your-wp-server.example.com;
proxy_ssl_server_name on;
proxy_set_header Host your-wp-server.example.com;
proxy_set_header X-Original-Host $host;
proxy_set_header X-Original-Proto $scheme;
}
5. Pass the real host name and trust it in wp-config.php
The proxy sends the WordPress server's own host name in the Host header, not your public domain. WordPress core mostly copes, but many page builders and plugins read HTTP_HOST directly and break.
Add this to the top of wp-config.php. It only accepts the header when it contains your real domain, so nobody can inject a fake host name by calling the origin server directly:
// Trust the public host and protocol sent by the edge proxy.
$public_host = 'yourdomain.com';
if ( ( $_SERVER['HTTP_X_ORIGINAL_HOST'] ?? '' ) === $public_host ) {
$_SERVER['HTTP_HOST'] = $public_host;
if ( ( $_SERVER['HTTP_X_ORIGINAL_PROTO'] ?? '' ) === 'https' ) {
$_SERVER['HTTPS'] = 'on';
}
}
The HTTPS line matters: without it, WordPress may think the site runs on plain HTTP and redirect in a loop or load mixed content.
Tip: use a custom header name like
X-Original-Hostinstead ofX-Forwarded-Host. Hosting providers often run their own reverse proxies, which can overwrite the standard headers.
For extra safety, configure the WordPress server or its firewall to accept traffic only from your edge proxy, so visitors can't reach it directly.
6. Check the headers arrive
Add a temporary log line to wp-config.php to confirm the proxy is sending what you expect:
file_put_contents( __DIR__ . '/host-debug.log',
json_encode( array(
'HTTP_HOST' => $_SERVER['HTTP_HOST'] ?? null,
'HTTP_X_ORIGINAL_HOST' => $_SERVER['HTTP_X_ORIGINAL_HOST'] ?? null,
'HTTP_X_ORIGINAL_PROTO' => $_SERVER['HTTP_X_ORIGINAL_PROTO'] ?? null,
) ) . "\n",
FILE_APPEND
);
Visit the site once and check that host-debug.log shows your live domain. Then delete both the test code and the log file straight away: the log sits in a public folder.
Pre-launch checklist
Before handing the site to your content team, test:
- Main URLs: the blog home page and inner pages load on the public domain.
- New pages: create and publish a brand-new page from wp-admin.
- Page builders: open Elementor, Divi or the block editor and check that AJAX features work without errors.
- Trailing slashes: visit
yourdomain.com/blogwithout the slash and check that the 301 redirect keeps the/blogprefix. - REST API and media:
/blog/wp-json/responds, and media uploads show at the right URLs. - Logins and HTTPS: wp-admin loads over HTTPS with no redirect loop.
- Caching: turn on your caching plugin or edge cache (for example Varnish or Cloudflare) and test everything again.
Summary
Stripping the /blog prefix at the edge looks like a shortcut, but it creates a permanent conflict between what WordPress expects and what the server receives. Taking one extra minute to match the folder on disk to the public URL saves hours of debugging later, and passing the real host and protocol safely keeps page builders and plugins happy.
Want WordPress added to your existing site under /blog, or a reverse-proxy setup that keeps breaking fixed properly? I set up and migrate WordPress hosting for clients worldwide. Tell me about your setup and I'll reply within a few hours.
