You click /wp-admin, and instead of your dashboard you get a spinning tab, a blank white screen, or a cheery “Too many redirects.” This is the moment where WordPress stops being a website and becomes a system you operate.
The good news: wp-admin failures are usually not “mystical WordPress stuff.” They’re regular web stack problems—routing, auth cookies, PHP workers, database latency, disk I/O, bad deploys—just wearing a WordPress hoodie.
Fast diagnosis playbook
If you’re on-call, don’t philosophize. Triage. Your goal is to answer three questions quickly:
- Is the request reaching the server? (DNS, CDN/WAF, TLS, vhost, routing)
- Is the web server handing it to PHP correctly? (Nginx/Apache config, PHP-FPM health, timeouts)
- Is WordPress itself choking? (plugin/theme fatals, DB connection/latency, object cache, filesystem permissions)
Check first (30–90 seconds): edge and HTTP status
- From your laptop:
curl -I https://example.com/wp-admin/andcurl -I https://example.com/wp-login.php. - Look at status codes: 200/302/401/403/404/500/502/503/504. They’re not decoration; they are the map.
- If you see 301/302 loops, focus on scheme/host normalization and cookie domains.
- If you see 502/504, focus on PHP-FPM or upstream timeouts (or the app taking too long).
- If you see 403, focus on WAF rules, basic auth, file permissions, or security plugins.
Check second (2–5 minutes): logs with intent
- Nginx/Apache error logs for the exact timestamp of your request.
- PHP-FPM logs and slowlog (if enabled) for stuck workers.
- WordPress debug log (only if you can enable safely) for plugin/theme fatals.
Check third (5–15 minutes): resource bottlenecks
- CPU saturation, memory pressure, OOM killer, disk I/O wait, filesystem full.
- Database connectivity and query latency.
- Object cache (Redis/Memcached) availability and timeouts.
Rule of thumb: wp-admin is more “dynamic” than the front page. A cached homepage can look fine while wp-admin is on fire.
What wp-admin actually does (and why it fails)
/wp-admin is not a single page. It’s a collection of PHP entry points (notably wp-admin/admin.php) that quickly fan out into:
- Authentication checks (cookies, nonces, user roles)
- Database reads (user meta, options, plugin states)
- Filesystem reads (plugins, themes, translations)
- HTTP calls (some plugins call external APIs on admin pages—yes, really)
- AJAX endpoints (
admin-ajax.php) that can hang independently
So when “wp-admin won’t open,” you’re usually looking at one of these categories:
- Network/edge blockage: DNS wrong, CDN/WAF blocks, TLS mismatch, wrong origin.
- HTTP routing/config bug: rewrite rules, vhost mismatch, misrouted PHP handler, broken
fastcgi_param. - Application fatal: plugin/theme syntax error, missing file, incompatible PHP version.
- Dependency failure: DB down/slow, Redis down, NFS/EFS stall, disk full, inode exhaustion.
- Resource exhaustion: PHP-FPM pool saturated, memory limit, max execution time, locked sessions.
- Redirect/auth loop: site URL mismatch, HTTPS offload misconfigured, cookie domain issues.
One quote to tape above your monitor:
“Hope is not a strategy.” — Gene Kranz
wp-admin outages reward method, not optimism.
Interesting facts and history (that actually helps you debug)
- Fact 1: WordPress began as a fork of b2/cafelog in 2003, and its plugin system grew into a compatibility minefield—powerful, but easy to break with one bad update.
- Fact 2:
wp-login.phpand/wp-admin/are separate entry points; a failure in one doesn’t always imply a failure in the other. Check both. - Fact 3: WordPress historically assumed “LAMP-style” deployments; modern stacks (Nginx + PHP-FPM, reverse proxies, containers) require extra headers and scheme awareness to avoid redirect loops.
- Fact 4: The “White Screen of Death” became famous because PHP fatals used to render nothing to the browser in production configs—logs were the only truth.
- Fact 5: Many admin requests are effectively uncached and user-specific, so they expose database and PHP capacity issues first—your cached homepage can keep smiling while your dashboard is unavailable.
- Fact 6: The options table (
wp_options) is a hotspot: autoloaded options are read constantly. A bloated autoload payload slows every admin page. - Fact 7: Since WordPress 5.2, “fatal error protection” can email admins and recover in some cases—but it won’t save you from infrastructure problems, and it won’t fix a broken opcode cache.
- Fact 8: REST API and admin-ajax endpoints are common collateral damage from security rules; a WAF that “helps” can silently kneecap wp-admin UI actions.
Start from first principles: classify the failure
1) Can you reach the origin at all?
If your browser can’t reach the site, wp-admin is irrelevant. Verify DNS, CDN, and TLS first. A lot of “WordPress problems” are actually “the load balancer is talking to the wrong pool.”
2) What HTTP code do you get, consistently?
- 301/302 loop: URL/scheme mismatch, reverse proxy headers, cookie issues, canonicalization wars.
- 403: WAF, IP allowlist, basic auth, file permissions, security plugin, ModSecurity rules.
- 404: vhost mismatch, rewrite rules, wrong docroot, missing WordPress files, multisite path confusion.
- 500: PHP fatal, misconfig, memory limit, broken plugin/theme, bad opcode cache, file permission errors.
- 502/504: Nginx can’t talk to PHP-FPM, PHP-FPM is overloaded/hung, upstream timeout, or the app is waiting on DB/disk.
- 503: maintenance mode, overloaded origin, deploy in progress, or an upstream is intentionally shedding load.
3) Is it only wp-admin, or all dynamic pages?
Compare:
/(often cached)/wp-login.php(less plugin code than full admin)/wp-admin/(more checks, more plugin hooks)/wp-admin/admin-ajax.php(hot path for dashboards)
Joke #1: A caching layer is like a fancy tablecloth—it can hide the mess, but it doesn’t do the dishes.
4) Decide: config, capacity, or code?
Everything you do should aim to place the incident in one bucket:
- Config: proxy headers, SSL termination, rewrite rules, PHP handler, file ownership, SELinux/AppArmor.
- Capacity: PHP-FPM pool, DB connections, IOPS, memory, CPU, Redis.
- Code: plugin/theme update, PHP version change, corrupt WordPress core, custom mu-plugin.
Practical tasks: commands, outputs, and decisions
Below are field-tested tasks you can run on a typical Linux host. They’re written for realism: you run a command, read the output, and make a decision. If you’re in containers, run the equivalent inside the relevant pod/container.
Task 1: Reproduce the failure from the server and capture status/redirects
cr0x@server:~$ curl -sS -o /dev/null -D - https://example.com/wp-admin/ | head -n 20
HTTP/2 302
date: Sat, 27 Dec 2025 10:12:41 GMT
location: https://example.com/wp-login.php?redirect_to=https%3A%2F%2Fexample.com%2Fwp-admin%2F&reauth=1
set-cookie: wordpress_test_cookie=WP%20Cookie%20check; path=/; secure; HttpOnly
server: nginx
What it means: You’re getting a normal redirect to login. If users claim “wp-admin won’t open,” check whether login itself works and whether cookies are being set/returned.
Decision: If you see a loop of location: bouncing between HTTP and HTTPS or between domains, jump to redirect loop diagnosis (Tasks 4–6).
Task 2: Check whether wp-login.php loads but wp-admin doesn’t
cr0x@server:~$ curl -sS -o /dev/null -D - https://example.com/wp-login.php | head -n 15
HTTP/2 200
date: Sat, 27 Dec 2025 10:12:55 GMT
content-type: text/html; charset=UTF-8
server: nginx
What it means: The login page is reachable. If wp-admin times out or 500s but wp-login is fine, suspect plugin hooks during admin bootstrap, DB slowness after auth, or PHP-FPM capacity.
Decision: Focus on PHP-FPM, DB, and WordPress fatals rather than DNS/CDN.
Task 3: Identify the exact upstream error (Nginx example)
cr0x@server:~$ sudo tail -n 40 /var/log/nginx/error.log
2025/12/27 10:13:10 [error] 1842#1842: *991 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.10, server: example.com, request: "GET /wp-admin/ HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"
What it means: Nginx talked to PHP-FPM but didn’t get a response header before the timeout. This is almost never “an Nginx bug.” It’s PHP stuck, overloaded, or waiting on DB/disk/network.
Decision: Check PHP-FPM pool health and slow requests (Tasks 7–9), then DB (Task 12).
Task 4: Detect redirect loops and where they start
cr0x@server:~$ curl -sS -o /dev/null -D - -L --max-redirs 10 https://example.com/wp-admin/ | egrep -i 'HTTP/|location:'
HTTP/2 301
location: http://example.com/wp-admin/
HTTP/1.1 301 Moved Permanently
location: https://example.com/wp-admin/
HTTP/2 301
location: http://example.com/wp-admin/
HTTP/1.1 301 Moved Permanently
location: https://example.com/wp-admin/
What it means: You’re bouncing between HTTP and HTTPS. That’s reverse proxy / SSL termination header trouble, or WordPress siteurl/home disagreeing with reality.
Decision: Fix scheme detection: ensure X-Forwarded-Proto is set by the proxy and honored by WordPress (Task 6), and validate DB URL settings (Task 11).
Task 5: Confirm what the server thinks the canonical host/scheme is
cr0x@server:~$ php -r 'echo "HTTPS=" . ($_SERVER["HTTPS"] ?? "");'
HTTPS=
What it means: In CLI this is empty, but it reminds you of the real issue: WordPress decides “am I HTTPS?” based on server variables. Behind a proxy, those variables are often wrong unless configured.
Decision: Ensure the web server passes correct fastcgi params and your proxy injects forward headers.
Task 6: Verify reverse proxy headers reach PHP (Nginx + PHP-FPM)
cr0x@server:~$ sudo grep -R "X-Forwarded-Proto" -n /etc/nginx/sites-enabled | head
/etc/nginx/sites-enabled/example.conf:42:proxy_set_header X-Forwarded-Proto $scheme;
What it means: Header is set for proxied locations, but wp-admin might be served via fastcgi, not proxy. Different blocks, different behavior.
Decision: If you terminate TLS upstream, configure WordPress with $_SERVER['HTTPS']='on' logic (often via wp-config.php) based on X-Forwarded-Proto, and ensure your load balancer actually sends it.
Task 7: Check PHP-FPM status: are workers saturated?
cr0x@server:~$ sudo systemctl status php8.2-fpm --no-pager
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.2-fpm.service; enabled)
Active: active (running) since Sat 2025-12-27 09:41:03 UTC; 33min ago
Main PID: 912 (php-fpm8.2)
Status: "Processes active: 28, idle: 0, Requests: 18452, slow: 37, Traffic: 2.1req/sec"
What it means: Idle is 0. That’s a smoking gun: the pool is fully busy. Requests queue up, Nginx times out, wp-admin “won’t open.”
Decision: Either add capacity (increase pm.max_children cautiously, scale out) or reduce per-request cost (fix slow DB queries, disable bad plugins, address disk stalls).
Task 8: Inspect PHP-FPM pool config for limits that match your symptoms
cr0x@server:~$ sudo egrep -n 'pm\.max_children|pm\.max_requests|request_terminate_timeout' /etc/php/8.2/fpm/pool.d/www.conf | sed -n '1,120p'
56:pm.max_children = 20
62:pm.max_requests = 500
115:request_terminate_timeout = 60s
What it means: With max_children 20, wp-admin bursts can saturate quickly. A 60s terminate timeout might kill legitimate slow operations (like plugin updates), but also prevents total wedging.
Decision: If you’re seeing timeouts at ~60s, this might be self-inflicted. Tune based on CPU/memory and real request profiles, not vibes.
Task 9: Identify whether PHP is dying from memory exhaustion
cr0x@server:~$ sudo tail -n 30 /var/log/php8.2-fpm.log
[27-Dec-2025 10:14:02] WARNING: [pool www] child 2143 said into stderr: "PHP Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 4096 bytes) in /var/www/html/wp-includes/class-wpdb.php on line 2345"
What it means: Classic: memory limit hit. wp-admin tends to load more code paths and bigger arrays.
Decision: Raise PHP memory limit (carefully) and find the underlying cause (often a plugin doing heavy admin queries). Don’t just crank it until the host swaps itself into the abyss.
Task 10: Confirm WordPress core files exist and are readable
cr0x@server:~$ ls -la /var/www/html/wp-admin | head
total 172
drwxr-xr-x 9 www-data www-data 4096 Dec 26 22:01 .
drwxr-xr-x 5 www-data www-data 4096 Dec 27 09:55 ..
-rw-r--r-- 1 www-data www-data 7339 Dec 26 22:01 admin.php
-rw-r--r-- 1 www-data www-data 1058 Dec 26 22:01 index.php
What it means: Files are present and owned by the runtime user. If you see weird owners (like a CI user) or missing files, core may be partially deployed or clobbered.
Decision: Fix file ownership and restore a known-good WordPress core if needed. Partial updates cause bizarre admin-only breakage.
Task 11: Verify the WordPress site URLs stored in the database
cr0x@server:~$ mysql -N -e "SELECT option_name, option_value FROM wp_options WHERE option_name IN ('siteurl','home');"
siteurl https://example.com
home https://example.com
What it means: If these are wrong (http vs https, wrong domain, stale staging URL), wp-admin redirects can loop forever or bounce to the wrong host.
Decision: Correct them in DB (or override temporarily in wp-config.php during incident response). Then fix the process that allowed drift.
Task 12: Check MySQL/MariaDB health and slow queries right now
cr0x@server:~$ mysql -e "SHOW PROCESSLIST;" | head -n 15
Id User Host db Command Time State Info
214 app 10.0.2.15:51122 wp Query 28 Sending data SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'
221 app 10.0.2.15:51148 wp Query 31 Locked UPDATE wp_options SET option_value = '...' WHERE option_name = 'rewrite_rules'
What it means: Autoload option reads taking tens of seconds and an UPDATE locked: that’s a real reason wp-admin hangs. Admin pages read options constantly; lock contention there is poison.
Decision: Identify the lock holder, reduce lock contention, and investigate bloated options/autoload payloads. If necessary, kill runaway queries during outage and fix the underlying plugin/task.
Task 13: Find the lock holder (InnoDB) and decide whether to kill it
cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | egrep -n "LATEST DETECTED DEADLOCK|TRANSACTION|WAITING" | head -n 30
103:TRANSACTION 4211892, ACTIVE 62 sec starting index read
110:LOCK WAIT 12 lock struct(s), heap size 1136, 6 row lock(s)
121:MySQL thread id 221, OS thread handle 140520112060160, query id 981231 10.0.2.15 app updating
What it means: You have a lock wait lasting ~60 seconds. WordPress admin requests can pile up behind this and saturate PHP-FPM.
Decision: During incident response, consider killing the blocking session if it’s safe (KILL 221;) and you understand the side effects (partial updates, plugin installs). Then follow up with the root cause.
Task 14: Check disk space and inode exhaustion (silent killers)
cr0x@server:~$ df -h /var/www/html /var/lib/mysql
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 80G 79G 650M 99% /
/dev/vdb1 200G 120G 81G 60% /var/lib/mysql
cr0x@server:~$ df -i /var/www/html
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda1 5242880 5239011 3869 100% /
What it means: You’re out of inodes. That can break sessions, uploads, plugin updates, cache writes, and logs. It manifests as “wp-admin is broken” in creatively unhelpful ways.
Decision: Free inodes (clean temp/cache directories, old releases, log rotation) and add monitoring. Disk-full is predictable; treat it like a process failure, not an act of nature.
Task 15: Identify I/O wait that makes everything “slow”
cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (server) 12/27/2025 _x86_64_ (4 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
12.10 0.00 4.22 62.35 0.00 21.33
Device r/s w/s rkB/s wkB/s aqu-sz await %util
vda 4.20 85.30 92.1 941.4 8.12 91.3 98.7
What it means: 62% iowait and near-100% disk util. Your “WordPress problem” is actually storage latency. wp-admin makes lots of small reads (PHP files, sessions, options), so it suffers early.
Decision: Reduce I/O (disable chatty logging, fix backup jobs, move sessions/object cache to memory, improve storage tier) or scale to more IOPS. Don’t tune PHP timeouts to “fix” a storage meltdown.
Task 16: Confirm Redis object cache health (if used)
cr0x@server:~$ redis-cli -h 127.0.0.1 ping
PONG
cr0x@server:~$ redis-cli -h 127.0.0.1 info stats | egrep 'instantaneous_ops_per_sec|rejected_connections'
instantaneous_ops_per_sec:1874
rejected_connections:0
What it means: Redis is alive and not rejecting connections. If Redis is down and WordPress is configured to use it, wp-admin can stall on connection timeouts depending on client settings.
Decision: If Redis is failing, either restore it or temporarily disable the object cache drop-in (wp-content/object-cache.php) to restore admin access.
Task 17: Disable plugins without wp-admin access (the safe-ish way)
cr0x@server:~$ cd /var/www/html/wp-content
cr0x@server:~$ mv plugins plugins.disabled
cr0x@server:~$ mkdir plugins
cr0x@server:~$ chown -R www-data:www-data plugins
What it means: WordPress won’t load plugins if the directory is empty. This is the fastest way to confirm “plugin caused admin death.”
Decision: If wp-admin returns, restore plugins one by one (or binary search) to find the offender. Then update/replace it. If it doesn’t return, plugins aren’t the primary cause—move on.
Task 18: Switch to a default theme if the admin is theme-hooked into oblivion
cr0x@server:~$ mysql -N -e "UPDATE wp_options SET option_value='twentytwentyfour' WHERE option_name IN ('template','stylesheet'); SELECT option_name, option_value FROM wp_options WHERE option_name IN ('template','stylesheet');"
template twentytwentyfour
stylesheet twentytwentyfour
What it means: This forces a default theme. Some themes (or theme frameworks) hook into admin and can break it.
Decision: If admin recovers, your theme is the culprit or exposed a PHP version issue. Fix in staging, then redeploy safely.
Task 19: Check for “maintenance mode” stuck on
cr0x@server:~$ test -f /var/www/html/.maintenance && echo "maintenance file present" || echo "no maintenance file"
maintenance file present
What it means: A failed update can leave WordPress in maintenance mode, returning 503-ish behavior or a “Briefly unavailable” page.
Decision: Remove .maintenance only if you’re sure no update is currently running. Then re-run the update properly.
Task 20: Enable WordPress debug logging temporarily (controlled blast radius)
cr0x@server:~$ sudo -u www-data bash -lc "grep -n \"WP_DEBUG\" -n /var/www/html/wp-config.php | head"
90:define('WP_DEBUG', false);
91:define('WP_DEBUG_LOG', false);
92:define('WP_DEBUG_DISPLAY', false);
What it means: Debug is off (good). When wp-admin is down due to fatals, you may need logs. But don’t spray errors into HTML in production.
Decision: Set WP_DEBUG_LOG to true and keep WP_DEBUG_DISPLAY false, reproduce once, then turn it back off and tail wp-content/debug.log.
Joke #2: Turning on debug in production is like turning on the cabin lights during turbulence—useful, but nobody relaxes afterward.
Common mistakes: symptom → root cause → fix
“Too many redirects” on /wp-admin
Symptom: Browser error or curl shows alternating 301/302 between http/https or between domains.
Root cause: siteurl/home mismatch, proxy doesn’t set X-Forwarded-Proto, WordPress thinks it’s HTTP while the client is HTTPS, or a canonical redirect plugin is fighting the load balancer.
Fix: Ensure correct forward headers; correct siteurl/home; add explicit HTTPS detection in wp-config.php when behind TLS termination; remove duplicate redirect logic.
Blank white screen (no HTML, no error)
Symptom: wp-admin returns an empty page, sometimes 200.
Root cause: PHP fatal with display_errors off; opcode cache confusion after deploy; broken plugin/theme; memory exhaustion.
Fix: Check PHP-FPM logs and web server error logs; enable WP debug log briefly; roll back plugin/theme update; clear opcache via service restart if appropriate.
502 Bad Gateway from Nginx (or 504 Gateway Timeout)
Symptom: Nginx returns 502/504; error log mentions upstream timed out or connect() failed to php-fpm socket.
Root cause: PHP-FPM down, socket path mismatch, pool saturated, or PHP stuck waiting on DB/disk/network.
Fix: Restart PHP-FPM if it’s wedged; fix socket permissions/paths; increase capacity; find and remove the slow path (plugin, DB lock, storage latency).
403 Forbidden only on wp-admin
Symptom: Homepage loads; wp-admin is 403.
Root cause: WAF rule triggered by admin cookies or query strings; basic auth misapplied; security plugin blocking IP; filesystem permissions on wp-admin.
Fix: Review WAF logs; whitelist admin paths carefully; confirm Nginx/Apache location blocks; fix ownership/permissions; verify no accidental deny rules.
“Error establishing a database connection” when hitting admin
Symptom: DB error page, often intermittent during load.
Root cause: DB down, max connections reached, network issues, DNS for DB host broken, or slow queries causing connection pile-ups.
Fix: Check DB health, processlist, and connection limits; scale DB; implement/query optimization; ensure app uses persistent connections only if you actually understand the consequences.
Login works, but wp-admin loads forever
Symptom: Credentials accepted; dashboard never finishes.
Root cause: admin-ajax.php calls hanging due to a plugin, blocked REST API, or slow external API call from within admin.
Fix: Inspect browser dev tools network tab for stuck requests; tail logs for admin-ajax; disable plugins; block or timeout external calls; allow REST endpoints through security layers.
Only some users can access wp-admin
Symptom: Works for you, fails for coworkers; or works on mobile not corporate laptop.
Root cause: Cookie domain/path misconfig, SameSite issues behind SSO, corporate proxy caching or stripping headers, IP-based allowlist rules.
Fix: Normalize domain and scheme; check Set-Cookie attributes; avoid IP allowlists unless you control the network; verify proxy behaviors.
Three corporate mini-stories from the trenches
Incident #1: The wrong assumption (the “it can’t be DNS” edition)
A mid-size company ran WordPress for marketing pages and a gated partner portal living inside wp-admin. The homepage was behind aggressive caching at the CDN. Everything looked healthy from the outside: landing pages loaded instantly, uptime checks passed, executives stopped asking questions. Naturally, this is when the real problem started.
Sales ops reported that nobody could log in. Engineers tried the usual: clear cache, try incognito, blame cookies. Someone insisted, confidently, that “DNS is fine because the site loads.” That assumption held the incident hostage for two hours.
The catch: the CDN had two origins configured. Static pages were served from origin A (correct). Admin paths were routed to origin B (old environment) due to a stale path-based rule added during a prior migration. Origin B still answered, but with a different TLS cert and a different WordPress siteurl, so it bounced users in a redirect loop until browsers gave up.
The fix was boring: correct the CDN routing rules and purge the wrong behavior. The lesson was sharper: “the site loads” is not a health check. You need health checks that hit /wp-login.php and a real admin action endpoint, not just the cached homepage.
Incident #2: An optimization that backfired (object cache edition)
Another team chased performance. They added a Redis object cache drop-in to reduce database load. In staging it looked great: fewer DB queries, faster page generation, happy graphs. They rolled it into production during a low-traffic window and went home feeling responsible.
Three days later, wp-admin started intermittently timing out. The homepage still screamed because it was cached at the edge. The dashboard, however, became a coin toss. PHP-FPM workers accumulated in “busy” state. Nginx logs filled with upstream timeouts. The database looked quieter than usual, which was… suspiciously calming.
The real issue was network path and timeouts. Redis lived on a separate node. Under certain packet loss conditions, the Redis client would stall longer than the web tier’s upstream timeout. Each admin request would wait on cache reads, holding a PHP worker hostage. Enough hostages, and the pool saturates. Then everything looks like “WordPress is down,” when in fact your cache is down in the most annoying way: not dead, just slow.
The resolution involved setting sane client timeouts, adding Redis health monitoring, and implementing a “fail open” strategy for object cache where appropriate. Optimization didn’t fail because caching is bad; it failed because the team treated a new dependency as if it were as reliable as localhost.
Incident #3: The boring but correct practice that saved the day (release hygiene edition)
A different org ran multiple WordPress sites on shared infrastructure. Not glamorous. What they did have was a disciplined release practice: immutable deploy directories, a symlink switch, and a documented rollback that anyone on-call could execute at 3 a.m. without inventing new syntax.
One evening a plugin update introduced a PHP fatal error on admin bootstrap for a subset of requests—specifically when loading a settings page that called a missing function under PHP 8.2. Frontend pages stayed fine due to caching and because the fatal path was admin-only. The incident was reported as “admins can’t publish.” That’s a business outage, even if your uptime dashboard stays green.
The on-call engineer didn’t SSH in to “poke around.” They followed the runbook: switch symlink back to previous release, restart PHP-FPM to clear opcache, verify wp-admin health check, and only then start a root-cause analysis. Business operations resumed in minutes.
Later, they tested the plugin in a staging environment matching production PHP and pushed a fixed version. Nobody wrote a heroic postmortem about “staying up all night.” They wrote a quiet ticket: “Add admin-path synthetic checks and enforce plugin update gates.” Boring saved the day, again.
Checklists / step-by-step plan
Step-by-step: restore wp-admin access safely
- Confirm scope: Is it only you, only some networks, or everyone? Test from two vantage points (your laptop + server).
- Capture HTTP behavior: status code, redirect chain, and response headers for
/wp-admin/and/wp-login.php. - Look at web server error logs: match timestamp and request ID if you have one.
- Check PHP-FPM health: running, saturated, logging fatals, slow requests.
- Check DB health: connectivity, locks, slow queries, max connections.
- Check disk: space + inodes + I/O wait. This is where “random” behavior comes from.
- Rule out plugins fast: rename
wp-content/plugins. If admin returns, isolate the offender. - Rule out theme: switch to a default theme via DB update if necessary.
- Confirm canonical URL settings:
siteurlandhome. Fix mismatch causing loops. - Only then tune timeouts/capacity: timeouts are not a fix for broken dependencies; they’re a guardrail.
- Rollback if recent change: If wp-admin died after an update, don’t debate—roll back and stabilize, then investigate.
- Write down the root cause while it’s fresh: your future self is a different person with less context and more alerts.
Checklist: reverse proxy and HTTPS offload sanity
- Load balancer sends
X-Forwarded-ProtoandX-Forwarded-For. - Web server passes relevant headers/params to PHP.
- WordPress sees HTTPS correctly (especially for wp-admin cookies marked
secure). siteurlandhomematch the public URL, including scheme.- No duplicate redirect logic (load balancer + plugin + web server) fighting each other.
Checklist: capacity and dependency health
- PHP-FPM has idle workers under normal load.
- DB shows low lock waits and reasonable query times for options/users.
- Disk has headroom in space and inodes; backups aren’t colliding with peak traffic.
- Redis/Memcached is either healthy or configured to fail fast/fail open.
- Monitoring includes synthetic checks that hit
/wp-login.phpand a non-cached dynamic endpoint.
FAQ
Why does the homepage work but wp-admin is down?
Because your homepage is likely cached (CDN, page cache plugin, reverse proxy cache). wp-admin is user-specific and dynamic, so it hits PHP and the database directly. Admin failures are often capacity or dependency issues that caching masks.
What’s the fastest way to tell if a plugin is the problem?
Rename wp-content/plugins so WordPress loads zero plugins, then try wp-admin again. If it recovers, you’ve proven “plugin-induced failure.” Then restore plugins gradually to find the offender.
Is raising PHP memory limit a real fix?
Sometimes. If logs show memory exhaustion, raising the limit can restore access. But treat it as a tourniquet. The underlying cause is often an admin page doing heavy queries or loading enormous option payloads.
What causes “too many redirects” specifically on wp-admin?
Usually scheme/host confusion: TLS termination at a proxy without correct headers, mismatched home/siteurl, or a redirect plugin enforcing https while the origin thinks it’s http.
How do I debug a blank white screen without showing errors to users?
Check Nginx/Apache error logs and PHP-FPM logs first. If needed, enable WP_DEBUG_LOG while keeping WP_DEBUG_DISPLAY off, reproduce once, then revert. Read wp-content/debug.log.
Can Cloudflare/CDN/WAF block wp-admin even if the site loads?
Yes. WAF rules often target /wp-admin, wp-login.php, REST API, or admin-ajax patterns. Also, CDNs may route admin paths differently. Check edge logs and path-based origin routing rules.
How do I know if it’s PHP-FPM saturation versus a single slow dependency?
PHP-FPM saturation shows as “idle: 0” and queued requests, often with upstream timeouts. Then you must find why workers are busy: slow DB queries/locks, storage I/O wait, or external API calls. Don’t stop at “pool is full.”
What’s the most common database issue behind wp-admin hangs?
Lock contention and slow queries on the options table. Admin loads options constantly, and certain plugins update options in ways that hold locks longer than you’d expect.
Should I restart services during an incident?
Restarting PHP-FPM can be a valid tactical move if it’s wedged or opcache is corrupt after a deploy. Restarting blindly, repeatedly, without logs is how incidents become mysteries. Capture logs and symptoms first, then restart with intent.
How can I prevent wp-admin outages from going unnoticed?
Add synthetic monitoring that hits /wp-login.php and a dynamic endpoint (not cached). Alert on increased 5xx, redirect loops, and upstream timeouts. “Homepage 200 OK” is not a reliability strategy.
Conclusion: next steps that prevent repeats
When wp-admin won’t open, stop treating it as a WordPress riddle and start treating it like a production incident. Identify the failure class (redirects, 5xx, timeouts, auth loops), confirm where the request dies (edge, web server, PHP, DB, storage), and make the smallest safe change that restores access.
Do these next, in order:
- Add admin-path health checks (login page plus a dynamic endpoint) so caching can’t lie to you.
- Instrument the stack: web server upstream timing, PHP-FPM saturation, DB lock waits, disk I/O wait, and filesystem fullness (space and inodes).
- Make rollbacks easy: immutable releases, symlink swaps, and a one-command rollback runbook.
- Control plugin updates: staging that matches production PHP, and a gate for high-risk plugins that touch admin heavily.
- Decide on dependency posture: if you add Redis, NFS, WAF rules, or a new proxy layer, you also add a new way for wp-admin to fail. Engineer the timeouts and failure modes on purpose.
The dashboard isn’t special. It’s just your most honest endpoint—uncached, stateful, and unforgiving. Treat it accordingly.