Blog

  • What HTTP 500 Actually Means (and Which Log Tells You Why)

    What HTTP 500 Actually Means (and Which Log Tells You Why)

    A 500 error means the server accepted your request and then crashed while building the page — it is not a WordPress message, not a browser problem, and not something a cache purge will fix. The reason is written in exactly one place: the server’s error log, not the browser console. So the fastest path is always the same: note the time of the failure, open the PHP error log for that minute, and read the first fatal line.

    I have chased this error on my own sites and on client sites more times than I want to admit, and the pattern never changes: people start deactivating plugins one by one. The log already says which part failed. Here is the order I use now.

    The short version

    • 500 = the server failed while generating the page. PHP crashed, or the web server died before PHP even ran. The browser is shown a generic page on purpose.
    • The log you want is the PHP error log, or the nginx/Apache error_log if PHP never got a chance to run.
    • Read the first fatal error in the time window, not the last line. One crash usually drags a dozen warnings and timeouts behind it.
    • 502, 503 and 504 are different animals. Check the status code before you touch anything.
    • No log entry at all usually means .htaccess, a firewall rule, wrong permissions, or a PHP handler mismatch after a version change on the host.

    First, confirm which error you have

    The number matters: each one points at a different layer of the stack. I have wasted an hour reading PHP logs for what was actually an nginx timeout.

    Status What it means Where to look first
    500 Internal Server Error PHP threw a fatal error, or the web server hit a configuration problem it cannot recover from. PHP error log, then the web server error_log.
    502 Bad Gateway The web server asked PHP-FPM for the page and got garbage or nothing back. nginx/Apache error_log, PHP-FPM log (pool crashed, socket missing).
    503 Service Unavailable The server is deliberately refusing work: maintenance mode, overload, or the PHP pool is down. Host status page, server load, PHP-FPM pool status.
    504 Gateway Timeout The request took longer than the proxy was willing to wait. PHP max_execution_time, proxy timeouts, a slow external API call.

    If the page shows 502 or 504, skip straight to the server logs — WordPress never finished the request, so it has nothing to say.

    Where the log actually lives

    It depends on who runs PHP. Three places cover almost everything I see.

    1. Shared hosting (cPanel-style). The account usually has a logs directory next to public_html with a per-domain error log inside — on LiteSpeed servers it is named after the domain, like example.com.error.log. The cPanel Errors page lists recent PHP errors too, and it is the fastest way in when you only have the file manager.

    2. A VPS or dedicated server you manage. The web server and PHP-FPM keep their own logs:

    • nginx: /var/log/nginx/error.log
    • Apache on Debian/Ubuntu: /var/log/apache2/error.log
    • Apache on RHEL/AlmaLinux: /var/log/httpd/error_log
    • PHP-FPM: /var/log/php-fpm/www-error.log, or /var/log/php8.x-fpm.log depending on the package

    3. Inside WordPress. If you have file access, you can make WordPress keep its own log. This only catches errors that happen after WordPress has loaded, so it is a complement to the server log, not a replacement.

    // wp-config.php — log PHP errors to a file instead of printing them
    define( 'WP_DEBUG', true );
    define( 'WP_DEBUG_LOG', true );      // writes wp-content/debug.log
    define( 'WP_DEBUG_DISPLAY', false ); // keeps errors out of the HTML
    @ini_set( 'display_errors', 0 );

    Then follow the file while you reload the broken page:

    # shared hosting (adjust the account name and domain)
    tail -f /home/USERNAME/logs/example.com.error.log
    
    # VPS with nginx + PHP-FPM
    sudo tail -n 200 /var/log/nginx/error.log
    sudo tail -n 200 /var/log/php-fpm/www-error.log

    The log line that answers the question

    A typical PHP fatal looks like this, and every part of it is useful:

    [21-Oct-2026 13:43:07 UTC] PHP Fatal error:  Uncaught Error: Call to undefined function
    wc_get_orders() in /home/user/public_html/wp-content/plugins/acme-tools/acme.php:42
    Stack trace: #0 {main}  thrown in /home/user/public_html/wp-content/plugins/acme-tools/acme.php on line 42

    Four things to pull out of it:

    1. The timestamp. Compare it to the time the page broke. Logs are often in the server’s timezone or UTC, so account for the offset before deciding the entry is unrelated.
    2. The severity. Fatal error and Uncaught Error stop the request. Warning and Notice usually do not, and chasing those first wastes an afternoon.
    3. The path and line number. That path is usually the answer: it names the theme or plugin file that was running when the process died.
    4. The message. It names the mechanism: undefined function, memory exhausted, execution time exceeded, permission denied.

    A few messages come up again and again, and each one has a standard first move:

    • “Allowed memory size of N bytes exhausted” — something is loading more than the limit allows. Raise memory_limit a step to confirm, then find the code or import doing it.
    • “Maximum execution time exceeded” — a loop, a huge import, or a slow external request. If the same URL hangs in a browser tab, it is a page load, not a background job.
    • “Call to undefined function” — a plugin calling code that is not loaded: a plugin deleted while another depends on it, or a PHP extension missing on the new server.
    • “Cannot redeclare” — the same function defined twice, usually two plugins from the same codebase.

    When you see dozens of warnings in the same minute, ignore them until you find the fatal. The last line in a crash window is almost never the cause.

    My five-minute triage order

    1. Write down the URL and the time — one line in a notes app. “It broke earlier today” is not a time.
    2. Open the account error log from the panel’s file manager or the cPanel Errors page.
    3. Find the fatal in that window and read the file path. If it points into a plugin, you know the culprit.
    4. If the log is empty, test the three things that produce a 500 without a PHP entry: rename .htaccess to .htaccess.bak and reload (a bad rule leaves no PHP trace); check whether the host changed the PHP version overnight; check the panel’s firewall log for a blocked request.
    5. If it is a plugin, rename the plugins folder through the file manager so WordPress disables everything, then put plugins back in halves. Faster than the admin screen when the admin screen also returns 500.
    6. Check disk usage. A full disk or exhausted inode limit makes writes fail and pages return 500 with a log entry that looks unrelated.

    When it is not your code

    Some 500s start and end without you changing anything, and that is a strong signal. If several sites on the same server break in the same minute, the PHP-FPM pool or the web server went down and plugin bisecting will not help. If only one action fails — a form submission, a checkout — check the firewall log first, because a security rule update can start blocking a POST body it used to allow.

    Two checks settle it fast: does a plain HTML file in the same account load, and did the failure start without a deploy? A static file that loads while every PHP page fails points at the PHP layer, not the site. And if the log is empty while the site serves pages or links you did not write, treat it as a compromise rather than an error — my five-minute infection check covers the first things to look at, and the recovery walkthrough covers what to do when you find injected files.

    What I do after the fix

    The fix is only half the job. The other half is making the next 500 take one minute instead of one evening:

    • Keep the log path in the notes for every site — host, panel, log location, PHP version.
    • Leave WP_DEBUG_LOG on for staging, and WP_DEBUG_DISPLAY off everywhere.
    • Watch the size of debug.log. An unrotated debug log can fill a disk, and a full disk causes the exact 500 you were debugging.
    • Update plugins one at a time on staging, not twelve at once at midnight.

    If you are stuck in the middle of one right now and want a second pair of eyes, my contact page is the place to reach me. Otherwise: open your site’s error log today, before you need it. Ten minutes now saves the panic later, because you will already know where the answer lives.

  • How to Check If Your WordPress Site Is Infected in 5 Minutes

    How to Check If Your WordPress Site Is Infected in 5 Minutes

    You can tell whether a WordPress site is infected in about five minutes without installing anything: check five places in order — robots.txt, your sitemap, the administrator list, the plugin list, and the newest files in wp-content. If two or more of those come back with something you did not put there, treat the site as compromised and work from that assumption. The order matters because it runs from cheapest to slowest, and a single odd result is usually a false alarm while a pattern is not.

    I ran exactly this sequence on my own blog after it started serving Japanese spam, and the first check was enough to confirm it. Here is the whole thing, with what each result actually means.

    The short version

    • robots.txt — fetch it raw. Attackers add Disallow or Sitemap lines pointing at paths and domains you never created.
    • Sitemap — open /wp-sitemap.xml and look for URLs that are not your posts.
    • Users — filter the list to Administrators. Note anything you did not create; delete nothing yet.
    • Plugins — compare against what you remember installing. Unknown folders, missing authors, and deactivated-but-present plugins are the tells.
    • Files — sort wp-content by modified date. Anything changed in the last 48 hours that you did not touch is a suspect.

    Check 1: robots.txt takes ten seconds

    Open https://yoursite.com/robots.txt in a browser with no cache, or fetch it from the command line. A normal WordPress install has almost nothing in it:

    User-agent: *
    Disallow: /wp-admin/
    Allow: /wp-admin/admin-ajax.php
    
    Sitemap: https://yoursite.com/wp-sitemap.xml
    

    What you are looking for is anything extra. Injected lines usually look like this:

    User-agent: *
    Disallow: /go/
    Sitemap: https://example-spam-domain.top/sitemap.xml
    

    A Disallow on a path you never created is the attacker hiding a doorway directory from crawlers. A Sitemap line pointing off your domain is the attacker advertising theirs. Either one is a confirmed infection — nothing legitimate writes those lines into robots.txt.

    Note that the file may be generated by a plugin such as Yoast or Rank Math, or it may be a physical file on disk. If it is static, check its timestamp in your file manager as well: a modification date you cannot explain is its own signal.

    Check 2: the sitemap shows what got published behind your back

    Open https://yoursite.com/wp-sitemap.xml, or /sitemap_index.xml if a plugin manages it. Then open the posts sitemap itself and scan for words you would never write — brand names, a language you do not publish in, or a redirect path like /go/.

    Two different things show up here, and they mean different things:

    • URLs that resolve to real posts — someone published content through your dashboard. Check the post author and the revision history before deleting anything.
    • URLs that 404 or redirect — the entries were injected into the sitemap rather than created as posts. That points at a modified theme file or a plugin, not at a hijacked login.

    If your sitemap is clean but robots.txt carried an injected Sitemap: line, the attacker is advertising their own sitemap instead of yours. That is still an infection, and it still needs the same cleanup.

    Check 3: the administrator list

    In the dashboard go to Users and filter by Administrator. Count them, then look at the email domain and the username pattern of anyone you do not recognise. Attacker accounts very often use a throwaway address and a name that would look like a normal subscriber if you only glanced at it.

    Do not delete the account yet. Deleting a user while the malware is still live can trigger a re-creation routine on the next page load, and you lose the only evidence of how it got in. Note the username, change your own password, and enable two-factor authentication first. If a plugin on your site records last login, check whether the suspicious account has ever actually logged in: an administrator that has never logged in was probably created by a script, which means the entry point is still open.

    Check 4: plugins and themes

    Open Plugins → Installed Plugins and actually read the list rather than scanning for update badges. The tells are boring, which is exactly why they get missed:

    • A plugin you did not install, often with a generic name like “WP Cache Manager Pro” or unusual capitalisation.
    • A plugin that is deactivated but still present. A deactivated plugin is not executed, so malware packs usually pair it with a must-use plugin that re-activates it.
    • An author field that is empty, or a version number that does not match the plugin’s real release history.
    • A second theme next to your active one that you never installed.

    Now open wp-content/mu-plugins through your host’s file manager. Must-use plugins have no dashboard entry at all, load on every request, and cannot be deactivated from the admin. If that folder holds files you did not add, you have found the loader.

    Check 5: files that changed recently

    This is the slowest check and the most conclusive. Open the file manager or connect over SFTP, go to wp-content, and sort by last modified, newest first. Then look at three things.

    PHP files inside uploads/. There is no legitimate reason for an uploads folder to contain a .php file. One is enough to be certain: that is a backdoor.

    Core files. Files in wp-includes/ and wp-admin/ should change only when you update WordPress. If their timestamps do not line up with your last update, compare them against a fresh copy of the same version and diff the two.

    Your theme’s functions.php. Attackers like it because it loads on every request and scanners tend to ignore theme files unless they recognise the payload. Look for long base64 blobs, eval(), gzinflate(), or an unexplained block of code at the very top of the file.

    What the results mean

    What you found What it usually means Next step
    Injected lines in robots.txt Confirmed infection, spam injection Restore robots.txt, then find the loader in mu-plugins
    Unknown URLs in the sitemap Posts created through the dashboard, or an injected sitemap Check post authors and revisions
    An admin you did not create Dashboard credentials are compromised Change every password, enable 2FA, then remove the account
    Unknown or deactivated plugin Malware pack left behind after an earlier cleanup Remove the folder entirely, not just deactivate it
    PHP in uploads/ or a modified core file Active backdoor Full restore from a clean backup, then rotate keys and passwords
    Only old timestamps, nothing unusual Probably not infected Re-run in 24 hours; the checks are cheap

    A five-minute checklist you can repeat

    1. Fetch /robots.txt and read every line.
    2. Open the sitemap and scan for foreign URLs.
    3. Count administrators against who should be there.
    4. Read the plugin list and check mu-plugins.
    5. Sort wp-content by modified date and look at the top ten entries.

    Run it once a month, and again the moment something changes: a sudden drop in traffic, a manual action in Search Console, or a page ranking in search that you never wrote. The checks do not prevent an infection, but they shorten the gap between the infection and the cleanup — and that gap is the part that actually costs you traffic and trust.

    Start with robots.txt today. It is ten seconds, and it is the single check most likely to tell you the truth on the first try. If you have already found something, the full order of operations I used is written up in my post on recovering a hacked WordPress site. If you are unsure whether a specific file is legitimate, send me the first twenty lines through the contact page and I will tell you what it does. More write-ups like this live under the WordPress category.

  • My WordPress Site Was Hacked and Serving Japanese Spam — Exactly How I Recovered

    My WordPress Site Was Hacked and Serving Japanese Spam — Exactly How I Recovered

    On October 5, 2026, I opened imivantan.com and got a white page with five words: This page isn’t working. HTTP ERROR 500.

    I assumed it was a plugin conflict. It wasn’t.

    When I checked robots.txt, I found a list of sitemaps I had never created. When I opened sitemap.xml, my domain was serving a Japanese hardware store — complete with a “25th anniversary sale” banner, power tools, kerosene delivery and machine rental.

    My blog had been hacked. The attacker wasn’t interested in my content — they were using my domain to rank Japanese e-commerce pages.

    This is what I found, why I chose to rebuild instead of repair, and the checklist I used to come back clean.

    The short version

    • Every page on the site returned HTTP 500, while parts of the domain were still serving spam.
    • robots.txt had been rewritten to advertise sitemaps I never created.
    • sitemap.xml rendered a fake Japanese storefront in HTML instead of XML.
    • The spam sitemaps were regenerated daily — the kit was still active.
    • Wayback Machine showed the site was healthy on 18 June 2026, so the compromise happened after that.
    • The old WordPress install couldn’t be trusted, so I rebuilt it from scratch.

    What I actually found

    The homepage was dead. So was every other page I tried — posts, categories, even wp-login.php. That’s the HTTP 500 error in the screenshot below.

    But robots.txt was alive, and it looked like this:

    User-agent: *
    Allow: /
    
    Sitemap: https://imivantan.com/?sitemapindex.xml
    Sitemap: https://imivantan.com/?sitemap254.xml
    Sitemap: https://imivantan.com/?sitemap113.xml
    Sitemap: https://imivantan.com/?sitemap61.xml
    Sitemap: https://imivantan.com/?sitemap177.xml
    Sitemap: https://imivantan.com/?sitemap230.xml
    Sitemap: https://imivantan.com/goods.php?sitemap390.xml

    I have never used sitemap files with random numbers in the filename. I have never had a goods.php. And my site has never sold anything.

    The sitemap index pointed to hundreds of pages like /products/suicide/p072000115 and /composition/charm/n39zdzba245px.shtml. Opening the sitemap URL itself produced this:

    A fake Japanese hardware store page served from imivantan.com/sitemap.xml, with a 25th anniversary banner and Japanese product categories such as power tools and kerosene delivery

    Every spam URL I checked had a <lastmod> date of that same day. This wasn’t an old infection sitting quietly in a corner — the kit was generating new pages on a schedule.

    What is the “Japanese keyword hack”?

    It’s a known pattern, documented by Google as the Japanese keyword hack. Attackers compromise a site — very often an outdated WordPress install — and inject pages, sitemaps and redirects that target Japanese search queries. The pages usually promote products the site owner has nothing to do with: tools, supplements, manga sets, rental services.

    The site owner often doesn’t notice, because the hacked pages are hidden from normal visitors or served only to search engines. In my case the main site was already broken, which is how I found it.

    Why should you care even if your site is small? Because the damage isn’t the spam pages themselves. It’s what Google does when it finds them: security warnings, manual actions, lost trust — and a domain that suddenly has to prove it’s legitimate again.

    The timeline

    Timeline: 18 June 2026 last healthy archive, 5 October 2026 site down with Japanese spam, 21 October 2026 rebuilt on clean WordPress

    Three dates matter. The site was healthy in the Wayback Machine on 18 June 2026. By 5 October it was down and serving spam. I rebuilt it on 21 October.

    How I diagnosed it (five checks you can run today)

    1. Check the status code. curl -I https://yourdomain.com/ — a 500 on every page usually means the PHP application is broken, not that the whole server is down.
    2. Read robots.txt. Unknown sitemap URLs are the fastest red flag. Attackers rewrite this file because search engines read it first.
    3. Open sitemap.xml. It should be XML. If it renders a web page — especially in another language — your rewrites have been hijacked.
    4. Check the Wayback Machine. The CDX API gives you a quick history: web.archive.org/cdx/search/cdx?url=yourdomain.com*&output=json. Find the last capture that looks normal.
    5. Open Google Search Console. Look at Security Issues and Manual Actions. If there’s a hack warning, you’ll see it there before you see it in traffic.

    Why I rebuilt instead of cleaning

    I could have tried to remove the spam files and patch the old install. I didn’t, for four reasons:

    • The site was fully down. The old install wasn’t just infected — it wasn’t serving anything.
    • I couldn’t confirm the entry point. Without knowing how they got in, “cleaning” is guesswork. Backdoors are designed to survive cleanups.
    • There was no verified clean backup. A backup taken after the compromise just restores the compromise.
    • The content was small. Around ten posts — recoverable from the Wayback Machine and my own notes. A fresh install was faster and safer than forensics on a dead site.

    If you have a backup you know is clean, restoring is a perfectly good option. If you don’t — rebuild. Your site is not worth more than your domain’s reputation.

    The recovery checklist I used

    1. Preserve evidence first. Screenshot the error page, robots.txt and a few spam URLs. You’ll need them for your host, your developer and Google Search Console.
    2. Check the server, not just the site. Look at other sites on the same hosting account, cron jobs, mail logs, FTP/SSH users and hosting panel accounts. One hacked site on a shared server is everyone’s problem.
    3. Rebuild or restore. Fresh WordPress, or a backup you can prove is clean. Update core, themes and plugins before adding anything else.
    4. Rotate every credential. WordPress admin, database, SFTP/SSH, hosting panel, mailbox, registrar. All of them — assume the old ones are known.
    5. Harden the new install. Security plugin, two-factor authentication, no unused plugins or themes, XML-RPC disabled if you don’t use it, file editing off.
    6. Clean up for Google. In Search Console, check Security Issues, then submit a fresh sitemap and request a review if you were flagged. Make sure old spam URLs return 404 or 410.
    7. Set up backups you will actually test. Automatic, off-server, and restore one to prove it works.
    8. Monitor. Uptime monitoring plus a monthly look at robots.txt and sitemap.xml. Ten minutes a month is cheaper than a rebuild.

    What I changed on the new site

    The new install is deliberately boring. A clean WordPress, the default theme, two plugins, one admin account with a unique password stored in a password manager, and an email address on my own domain for site notifications.

    I also cleaned out the demo content that came with the install — sample pages, placeholder navigation, a footer full of links to nowhere. A small thing, but if you’re rebuilding anyway, start with a site you’d be happy to show someone.

    Still on the list: a security plugin with login protection, scheduled off-site backups, and Search Console verification for the new domain setup. I’ll write about those as I finish them.

    What this actually cost me

    About two hours of diagnosis, an evening of rebuilding, and a domain that now has to earn back trust. No dramatic traffic loss to report — the site was small and the blog was just starting. That’s the honest version, and it’s exactly why I didn’t try to save a broken install.

    Five things I’d tell anyone running WordPress

    • Your robots.txt and sitemap are public. Check them once a month. Most owners never do.
    • WordPress is a target. Not because it’s insecure by design, but because it’s popular. Updates are not optional.
    • A backup you haven’t restored is not a backup. Test one before you need one.
    • Hacks are usually invisible. This one didn’t deface my site — it quietly turned my domain into someone else’s storefront.
    • Own your platform. A self-hosted WordPress install is what let me rebuild in hours instead of waiting on a platform’s support queue.

    What’s next

    Now that the site is clean, the real work starts: publishing consistently, getting indexed again, and documenting what actually moves the needle for a small site in 2026.

    If you run WordPress, open yourdomain.com/robots.txt right now. If you see sitemap URLs you don’t recognize, start with step one of the checklist above — and don’t wait for the traffic to tell you something’s wrong.

    Ivan.T builds and runs small websites, including a hosting company and a PDF tools site. More about me.