The Case of the /index.php/ That Wouldn’t Leave

For longer than I’d like to admit, every URL on this blog wore a little badge of shame: /index.php/ wedged right into the middle of it. Not /2026/06/the-post/ like a civilized website, but /index.php/2026/06/the-post/, as if the server wanted everyone to know exactly which file was doing the work back there.

It also meant my RSS feed only existed at /index.php/feed/. Hit the normal /feed/ and you got a 404 — which is a great way to make sure absolutely nobody subscribes to you, including the three feed readers left on Earth.

I finally sat down to fix it. The fix took one line.

The obvious suspect, who didn’t do it

If you’ve ever Googled this problem, you already know the prime suspect: mod_rewrite. WordPress needs Apache’s rewrite engine to turn pretty URLs into something index.php can route, and the standard advice is “enable mod_rewrite.” So I checked:

apache2ctl -M | grep rewrite
 rewrite_module (shared)

There it was, loaded and present. mod_rewrite had an alibi. Whatever was breaking my URLs, it wasn’t that.

This is the part of the mystery where the detective stops looking at the loud, obvious person in the room and starts wondering about the quiet one nobody’s mentioned.

The quiet one: AllowOverride

Here’s the thing about how WordPress does permalinks. WordPress can’t edit your Apache config — it has no business poking around in /etc/apache2/. So instead it writes its rewrite rules into a .htaccess file sitting in the web root, and then trusts Apache to read it.

And Apache will read it. If you’ve told it to.

The switch is a directive called AllowOverride, and it lives in the <Directory> block for your site. Here’s what mine looked like:

<Directory /var/www/html/johnnycarlos.com/public_html>
        Require all granted
</Directory>

See the AllowOverride line? Neither do I. It isn’t there. And when it isn’t there, Apache uses the default — AllowOverride None — which means ignore .htaccess entirely.

So the full, infuriating picture was this:

  • WordPress dutifully wrote a perfect set of rewrite rules into .htaccess.
  • Apache walked past that file every single request like it was a flyer for a gym membership.
  • WordPress, getting no signal that rewriting works, fell back to the one structure guaranteed to work without rewrite support: PATHINFO. The one that bakes /index.php/ into every URL.

The rules were correct. The engine was running. The door was just locked from the inside.

The plot twist: there are two front doors

Before celebrating, one gotcha that trips up basically everyone running HTTPS. If you set up SSL with Certbot — which you should — Certbot splits your site into two vhosts:

  • :80 (HTTP), in your normal site .conf
  • :443 (HTTPS), in a -le-ssl.conf file Certbot generates

All your real traffic is served over HTTPS. So the <Directory> block that actually matters for permalinks is the one in the :443 vhost. You can add AllowOverride All to your :80 config, reload, pat yourself on the back, and change absolutely nothing, because nobody’s been served by that vhost since you installed the certificate.

Check both:

sudo grep -r -A2 "Directory /var/www/html/yoursite" /etc/apache2/sites-available/

Put the fix where the traffic is.

The one line

Add AllowOverride All to the <Directory> block — in the :443 vhost (and the :80 one too, for tidiness):

<Directory /var/www/html/johnnycarlos.com/public_html>
        AllowOverride All
        Require all granted
</Directory>

Sanity-check the config and reload:

sudo apache2ctl configtest
sudo systemctl reload apache2

Then make WordPress stop generating PATHINFO URLs. This part is non-negotiable and easy to forget: the permalink setting is still PATHINFO until you change it, regardless of whether .htaccess now works.

Settings → Permalinks → Post name → Save Changes.

Hitting Save is what flushes the rules into .htaccess and flips WordPress off the ugly structure. Even if the setting already looks right, the Save is the magic word.

That was it. Posts went clean. /feed/ came back to life. And every old /index.php/... link now 301-redirects to its tidy replacement, so nothing I’d already published broke — and the redirects are permanent, which keeps the SEO folks happy too.

The takeaway

The lesson I keep relearning: when the obvious fix is already done, the bug is in the thing the tutorial assumed you’d never get wrong. mod_rewrite gets all the press. AllowOverride does the quiet work of deciding whether mod_rewrite’s instructions are ever allowed to run — and the default is “no.” Nobody mentions it because in most stock configs it’s already set. Mine wasn’t, and so I got to spend an evening interrogating an innocent module.

Anyway. The /index.php/ is gone. The feed is back. And the world continues to ignore my blog which is the highest form of internet security there is.


Posted

in

by

Tags: