How to Move a WordPress Site to Subdirectory Without Breaking SEO, Admin, or PHP

Moving WordPress to /blog on a Django website can break SEO, admin panels, and PHP execution if done incorrectly. This deep dive explains why it fails and how to design a correct Nginx routing strategy.

February 1, 2026
•
Updated February 7, 2026
•
5 min read
•
67 views
How to Move a WordPress Site to Subdirectory Without Breaking SEO, Admin, or PHP

Why Moving WordPress to subdirectory Is an Infrastructure and SEO Problem (Not Just a URL Change)

Moving WordPress into a subdirectory like /blog on a backend-driven website (Django, Node, or any framework) sounds simple on paper.

In reality, it is one of the most underestimated infrastructure and SEO challenges in mixed-stack systems.

This guide explains why subdirectory migration matters, why it breaks in non-obvious ways, and how a correct Nginx routing strategy solves problems that generic tutorials never mention.


Why Moving WordPress to /blog Matters for SEO

Search engines treat subdomains and subdirectories very differently.

SEO realities that drive this decision

  • blog.example.com is often treated as a separate entity

  • Authority, backlinks, and trust are split

  • /blog inherits:

    • Domain age

    • Trust signals

    • Existing authority

Search engine preference (simplified)

example.com/blog   → authority consolidated
blog.example.com   → authority diluted

For content-heavy websites (travel, SaaS, documentation), keeping blogs inside the primary domain structure is a long-term SEO win.


Initial Architecture: Django on /, WordPress on Subdomain

A very common starting setup looks like this:

example.com          → Django backend
blog.example.com     → WordPress

At first glance:

  • Everything works

  • No routing conflicts

  • Teams are happy

The hidden cost

  • Blog SEO grows separately

  • Internal linking power is lost

  • Analytics and attribution get messy

This is usually when the decision to move WordPress to /blog is made.


The First Naive Attempt: “Just Proxy WordPress to /blog”

The initial idea is almost always:

“Just proxy WordPress behind /blog and keep Django on /.”

In Nginx terms, this often starts as:

location /blog {
    proxy_pass http://wordpress;
}

What goes wrong immediately

  • Redirect loops

  • Broken admin URLs

  • Incorrect cookie paths

  • PHP files downloading instead of executing

This happens because WordPress is not path-agnostic.

# Always normalize /blog URL
location = /blog {
    return 301 /blog/;
}
  • WordPress expects trailing slashes

  • Missing this causes redirect loops & cookie mismatch


The PHP Download Nightmare (application/octet-stream)

One of the most confusing failures:

PHP files start downloading instead of executing.

What this actually means

  • Nginx is serving PHP as a static file

  • PHP-FPM is never invoked

  • No error appears in WordPress logs

  • Browser receives raw PHP

Why this happens

Most setups accidentally combine:

  • alias

  • try_files

  • generic location ~ \.php$

In subdirectory setups, this silently breaks PHP execution paths.

This issue is misleading because:

  • Logs look clean

  • Nginx reloads fine

  • The site partially works


Redirect Loops That Make No Sense (/wp-admin)

Another classic symptom:

/blog/wp-admin → /blog/wp-admin → /blog/wp-admin

Infinite redirects with no clear reason.

Root causes

  • WordPress decides its “home” URL internally

  • Cookies are scoped incorrectly

  • Admin URLs assume root (/wp-admin)

  • Path-based installations confuse session handling

Clearing cookies sometimes “fixes” it — temporarily — which makes debugging even harder.


Admin Panel Loading Without CSS and JS

This failure is subtle and extremely misleading.

Symptoms

  • Login page loads

  • Admin dashboard loads

  • UI is completely broken

Browser console shows

Unexpected token '<'

What’s really happening

Requests like:

/blog/wp-admin/load-scripts.php

are:

  • Routed incorrectly

  • Served as HTML instead of JS

  • Or blocked entirely

This happens because WordPress admin is not a single entry point.

# WordPress content assets (NO PHP execution here)
location ^~ /blog/wp-content/ {
    alias /var/www/wordpress/wp-content/;
    expires 30d;
    access_log off;
}
  • “Notice there is no PHP handler here — static files must never reach PHP-FPM.”

# Critical admin JS loader
location = /blog/wp-admin/load-scripts.php {
    include fastcgi.conf;
    fastcgi_pass unix:/run/php/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME /var/www/wordpress/wp-admin/load-scripts.php;
}
  • Admin UI depends on this endpoint

  • If this fails → broken dashboard even if login works


Core Realization: WordPress Is Not “Just PHP”

This is the turning point.

Wrong assumption

“WordPress is a PHP app, so a generic PHP handler is enough.”

Reality

  • WordPress admin executes many PHP files directly

  • Some endpoints must never pass through index.php

  • Static and dynamic paths must be separated explicitly

WordPress inside a subdirectory must be treated as a manually routed PHP application, not a generic one.


Explicit PHP Routing Strategy (The Correct Model)

A stable setup follows explicit intent, not wildcard routing.

Execution paths (PHP)

  • wp-login.php

  • wp-admin/*.php

  • wp-admin/load-scripts.php

  • wp-admin/load-styles.php

  • Front controller (index.php)

Static-only paths

  • wp-content/

  • wp-includes/

  • Admin CSS / JS / fonts


Correct Request Flow (Diagram)

High-level routing flow

Browser
  |
  v
Nginx
  |
  |-- /static/        → Django static
  |-- /media/         → Django media
  |-- /blog/wp-content/ → WordPress static
  |-- /blog/wp-admin/*.php → PHP-FPM
  |-- /blog/          → WordPress index.php
  |-- /               → Django (Gunicorn)

Why this works

  • No ambiguous PHP handling

  • No double execution paths

  • Django and WordPress never overlap

  • SEO URLs remain clean


Why Generic Nginx + WordPress Tutorials Fail Here

Most tutorials assume:

  • WordPress at /

  • No secondary backend

  • No subdirectory execution

They usually rely on:

location ~ \.php$ { ... }

This breaks immediately when:

  • WordPress is nested

  • Django owns /

  • Admin routes need isolation


Final Architecture Principles (Conceptual, Not Copy-Paste)

A working solution follows these rules:

Rule 1: No global PHP handlers

Never use:

location ~ \.php$

Rule 2: WordPress must exist at ONE URL

Never allow:

  • Subdomain + subdirectory simultaneously

Rule 3: Django must be fully isolated

No WordPress fallback inside /

Rule 4: Static and PHP paths must be intentional

Explicit routing beats magic every time.


SEO Routing Flow Diagram

Search Engine
     |
     v
example.com/blog/slug
     |
     v
Nginx
     |
     v
WordPress index.php
     |
     v
Single canonical blog URL

This ensures:

  • No duplicate content

  • No redirect chains

  • Clean indexing


Lessons Learned from Subdirectory WordPress Migrations

  • WordPress internals are deeper than they look

  • SEO decisions often create infra complexity

  • PHP execution must be explicit

  • Nginx debugging requires request-level thinking

  • Mixed stacks need hard boundaries


Final Thoughts

Moving WordPress to /blog on a Django (or Node-based) website is not a copy-paste task.

It requires:

  • Understanding request flow

  • Respecting WordPress internals

  • Designing Nginx routing deliberately

When done correctly, the result is:

  • Clean SEO

  • Stable admin

  • Predictable deployments

  • Zero PHP surprises


Share this article

About the author

Aryan Chaturvedi
Aryan Chaturvedi
Turning Ideas into High-Performance Web Apps

Slow, bloated web apps lose users and rankings.
I build fast, scalable, SEO-ready web applications using Next.js, MERN, and Django.
My focus is clean architecture, performance, and long-term stability.
I don’t ship demos — I ship production-ready systems.

View all posts

Related Articles

Continue reading with these related posts