Close the WordPress 7.1.4 Update-Window Hole on Your Server
Update to WordPress 7.1.4, then deny web access to wp-content/upgrade/ in Apache or Nginx and prove it with curl, so every future update stays safe.
The short answer
WordPress 7.1.4, released on 28 October 2026, fixes a race condition where a full copy of WordPress sat at a guessable path under wp-content/upgrade/ for a few seconds during core updates. Update to 7.1.4 or your branch's fixed release, then add a web server rule that denies all requests to wp-content/upgrade/: Require all denied in an Apache Directory block, or location ^~ /wp-content/upgrade/ { deny all; } in Nginx. A curl -I request for a test file in that folder should then return 403, and WordPress keeps updating because it works with that folder on disk, not over the web.
Reading options
On this page
Update to WordPress 7.1.4 (or the security release for your branch), then add one web server rule that refuses every request to wp-content/upgrade/. The update shrinks the risk, and the rule closes the door for good, including on the next update and the one after that. Here is the surprising part: for a few seconds during every core update, older versions left a complete copy of WordPress, installer included, sitting at a guessable web address.
By the end of this guide you will have checked your version, taken a backup, added a deny rule for Apache or Nginx, and proved with curl that it works while updates keep running normally.
What the update-window hole actually is
When WordPress updates itself, it downloads a zip file and unpacks it into a working folder under wp-content/upgrade/ before copying the new files into place. That folder lives inside your website, so the web server will happily serve it to anyone who asks, unless you tell it not to.
According to Patchstack's write-up of the release, before 7.1.4 the working folder had a predictable name, at least for anything uploaded through the admin. The cleanup routine also deleted the index.php file that normally stops the folder from being listed. The unpacked copy includes wp-admin/setup-config.php, the installer script, which can run without your site's configuration.
Think of it like a spare house key left in a box on the porch for one minute while the movers carry boxes in. A minute is short. But if someone knows which box and when, a minute is enough.
Patchstack rates the attack as hard to pull off, and notes the window comes back on every update, including unattended background updates. No login is needed to try.
Why this matters: at the time of writing (11 November 2026), the US agency CISA lists this flaw, CVE-2026-119977, in its Known Exploited Vulnerabilities catalog. CISA added it on 3 November 2026 and describes it as a race condition that "could allow unauthenticated attackers to execute code on the server during the upgrade window" because of "predictable, web-accessible staging paths". It is the fourth WordPress core entry CISA has added to that catalog since July 2026.
What 7.1.4 changed
WordPress released 7.1.4 on 28 October 2026 and recommends updating immediately. The release notes list "A race condition during core updates that could lead to remote code execution" among its fixes, along with several cross-site scripting (XSS) issues. XSS means an attacker can sneak script code into a page that other visitors or admins then run.
Patchstack reports that 7.1.4 adds 16 random characters to the working folder name and keeps index.php in place. That makes the folder much harder to guess. It does not make the folder private, and that is why the server rule is still worth adding.
Where the block has to live
A security plugin is PHP code that runs inside your WordPress site. The unpacked copy in wp-content/upgrade/ is a separate WordPress that loads its own files, so as Patchstack puts it, your site's firewall never runs for that request. The block has to live one level lower, in Apache or Nginx, which answers before any PHP starts.
Before you start
Give yourself about 30 minutes for the first site. After that, each extra site takes five or ten.
- SSH access to the server, or at least file access to the site's root folder.
- Root or sudo access if you will edit the Apache vhost or the Nginx server block.
- WP-CLI installed (the
wpcommand), which makes checks much faster. The dashboard works too. - Your site's root path, for example
/var/www/example.com/public_html. Swap in your real path everywhere below. - A fresh backup of files and database, covered in Step 2.
- Risk level: low. A deny rule only affects web requests to one folder, and a config test before reload protects you from typos.
Heads up: if your site moved wp-content somewhere else with a custom content directory, the working folder moves with it. WordPress builds the path from the content directory, so adjust every path in this guide to match.
Part 1: Update WordPress first
Step 1: Check which version you are running
Run this from the site's root folder to print the installed version.
wp core versionYou should see a single version number such as 7.1.4. In the dashboard, go to Dashboard > Updates to see your version and any waiting update.
Then compare against the list of fixed releases. You are covered if you see 7.1.4 or the fixed version for your branch:
- 7.1 branch: 7.1.4
- 7.0 branch: 7.0.8
- 6.9 branch: 6.9.11
- 6.8 branch: 6.8.12
- 6.7 branch: 6.7.11
- Older branches: each has its own fixed release, all the way down to 4.7.39 for the 4.7 branch.
WordPress 4.6 and earlier get no security updates at all, so if you are on one of those, the only real fix is a major upgrade.
Quick tip: sites with automatic background updates should receive the release on their own. Still check, because a full disk or a file permission problem can quietly stop an automatic update.
Step 2: Back up files and database
The WordPress backup guidance says a full backup has two parts, the database and the files, and that you should always back up the database before an upgrade. It also says to keep copies in more than one place.
This command exports the database to a dated file in your home folder, outside the public web folder.
wp db export ~/example-db-$(date +%F).sqlYou should see a line starting with Success: Exported to. Next, this command packs the whole site folder into one archive, also outside the web root.
tar -czf ~/example-files-$(date +%F).tar.gz -C /var/www/example.com public_htmlWhen it finishes with no error, copy both files off the server, to your own computer or cloud storage. A backup that lives only on the server you are fixing is half a backup.
Step 3: Run the update
This command updates WordPress core to the latest release.
wp core updateA successful run ends with Success: WordPress updated successfully. If you prefer the dashboard, go to Dashboard > Updates and click the update button there.
Staying on an older branch on purpose? The --minor flag limits WP-CLI to minor releases in your branch, for example 6.8 to 6.8.12.
wp core update --minorRun wp core version again afterwards. It should now show 7.1.4 or your branch's fixed version.
Part 2: Block web access to the upgrade folder
Your site never needs a browser to fetch anything from this folder. WordPress itself works with it through PHP, directly on the disk. So the safe rule is simple: refuse every web request to that path.
Here is a made-up example: Sara runs a small bakery site on a VPS with automatic updates turned on. A background update starts at 03:00 while she sleeps. With the rule below in place, any stranger who requests a file under wp-content/upgrade/ during those seconds gets a flat 403 Forbidden, and the update finishes normally without her doing anything.
Pick the section that matches your web server. Not sure which you have? Run curl -sI https://example.com/ | grep -i '^server', though some hosts hide this header.
Step 4 (Apache): add the rule to the virtual host
This is the strongest option because it does not depend on any .htaccess file or AllowOverride setting. You need root access to edit the vhost file. On Debian and Ubuntu these usually live in /etc/apache2/sites-available/.
This block denies every request for anything inside the upgrade folder. Put it inside the site's <VirtualHost> section, next to the existing <Directory> block for the document root.
<Directory "/var/www/example.com/public_html/wp-content/upgrade">
Require all denied
</Directory>Require all denied is the Apache 2.4 way of saying "access is denied unconditionally". Next, test the config before reloading.
sudo apachectl configtestYou want to see Syntax OK (the Apache docs spell it Syntax Ok). Then reload Apache gently, which keeps current visitors connected.
sudo apachectl gracefulStep 4 (Apache on shared hosting): add a rewrite rule in the site root
On shared hosting you often cannot touch the vhost. In that case, use the .htaccess file in the site root, the same file that holds the # BEGIN WordPress block.
First, make a copy of the current file so you can undo in one move.
cp /var/www/example.com/public_html/.htaccess ~/htaccess-backup-$(date +%F)Then add these lines at the very top of the root .htaccess, above the # BEGIN WordPress line. The rule returns 403 Forbidden for the folder and everything inside it.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^wp-content/upgrade(/|$) - [F]
</IfModule>The [F] flag tells Apache to send a 403 Forbidden status and stop processing further rules. Keeping the block outside the WordPress markers matters, because WordPress rewrites the text between # BEGIN WordPress and # END WordPress when you save permalink settings.
Heads up: do not put a .htaccess file inside wp-content/upgrade/ itself. The WordPress reference code for the unpack step empties that folder before each update by deleting every entry it lists, and its directory listing includes hidden files by default. A rule file in there can disappear at exactly the wrong moment.
Step 4 (Nginx): add a location block
Open the server block for your site, often in /etc/nginx/sites-available/ or /etc/nginx/conf.d/. Add this block inside it.
location ^~ /wp-content/upgrade/ {
deny all;
}The ^~ part is not decoration. Nginx first picks the longest matching prefix location, then normally checks regular expression locations, such as the usual location ~ \.php$ block that sends PHP files to PHP-FPM. The official docs say that if the longest matching prefix has the ^~ modifier, "regular expressions and predicate locations are not checked".
Why this matters: with a plain location /wp-content/upgrade/ and no ^~, the regular expression location for .php files is still checked and can win, so PHP files inside the folder would go to PHP-FPM instead of being denied. That is the exact file type an attacker wants. With ^~, the regular expressions are skipped and the deny applies.
Next, test the config. Nginx checks the syntax and tries to open the files the config refers to.
sudo nginx -tYou should see syntax is ok and test is successful. Then reload without dropping connections.
sudo nginx -s reloadHow to check it worked
Do not trust the rule until you have watched it refuse a real file. A request for a file that does not exist can return 404 for many reasons and proves nothing. So create a harmless test file first.
This command puts a tiny text file inside the upgrade folder.
echo "block test" > /var/www/example.com/public_html/wp-content/upgrade/blocktest.txtNow ask for it over the web, along with a PHP path and the folder itself. Swap in your domain.
curl -sI https://example.com/wp-content/upgrade/blocktest.txt | head -n 1
curl -sI https://example.com/wp-content/upgrade/index.php | head -n 1
curl -sI https://example.com/wp-content/upgrade/ | head -n 1Each line should show 403 Forbidden, for example HTTP/1.1 403 Forbidden or HTTP/2 403. If the test file comes back with 200, the rule is not active yet. If it comes back 404 even though you just created it, something in front of the server, such as a CDN or a security layer, is answering first, so look there.
When you are done, delete the test file so the folder is clean again.
rm /var/www/example.com/public_html/wp-content/upgrade/blocktest.txtConfirm the folder is not listable
The third curl line above already covers this: a 403 on wp-content/upgrade/ means no directory listing can come back. On Nginx, listings are off unless someone turned on autoindex, whose default is off. On Apache, listings depend on Options Indexes, but the deny rule wins either way.
Confirm the folder is empty between updates
Patchstack's advice is that between updates this folder should hold nothing but an index.php. This command lists anything else in it.
find /var/www/example.com/public_html/wp-content/upgrade -mindepth 1 ! -name index.phpNo output is the result you want. Leftover folders from an old update are usually harmless debris, but treat unknown PHP files as a reason to investigate before you delete anything.
Confirm WordPress updates still work
Blocking web access does not touch the way WordPress updates itself. The update downloads the package from WordPress.org, and the reference code for the unpack step only uses local filesystem calls on the server: it lists, deletes and unzips files on disk under wp-content/upgrade/. There is no web request to your own site in that step. We could not run a full live WordPress update against the rule for this guide, so please confirm on your own server with the checks below.
First, check that the core files match the official checksums, which also shows the update landed cleanly.
wp core verify-checksumsThe success line is Success: WordPress installation verifies against checksums. Next, ask WordPress.org for updates, skipping the cached result.
wp core check-update --force-checkIf a newer release exists, it shows a table with the version, update type and package URL. The WP-CLI docs say the command also prints a success message when you are up to date.
The next real core update is the full test, because it unpacks into this folder. Plugin and theme updates also unpack there, so on a staging copy try one from Dashboard > Updates and then run the find command above to see the folder clean again.
Troubleshooting
The test file still returns 200 on Apache with the rewrite rule
Likely cause: Apache is ignoring .htaccess files for this site because the vhost has AllowOverride None, or mod_rewrite is not loaded. Fix: use the vhost <Directory> block from Step 4 instead, or ask your host to allow overrides for the site.
It worked, then stopped after a plugin changed its own rules
Symptom: the root .htaccess rule used to return 403, and now files under wp-content/upgrade/ return 200. Likely cause: a plugin wrote a .htaccess inside wp-content/ that contains RewriteEngine On. By default, mod_rewrite rules in a deeper folder replace the parent's rules instead of adding to them. Fix: move the protection to the vhost, or add RewriteOptions Inherit to that deeper .htaccess so it also runs the parent's rules.
500 Internal Server Error, log says "Require not allowed here"
Symptom: the whole folder, or the whole site, returns 500 after you put Require all denied in a .htaccess file, and the Apache error log shows .htaccess: Require not allowed here. Likely cause: Require in .htaccess needs the AuthConfig override, and your vhost only allows something narrower, such as AllowOverride FileInfo. Fix: use the mod_rewrite version from Step 4, which needs only the FileInfo override, or use the vhost block.
The vhost Directory rule is ignored
Symptom: apachectl configtest says Syntax OK, but the test file returns 200. Likely cause: somewhere in the config a <Location /> section says Require all granted. Apache's docs warn that Location sections, by default, overwrite authorization set in Directory and Files sections. Fix: search the config for <Location and either remove the broad grant or place the deny in a matching <Location "/wp-content/upgrade/"> section instead. The AuthMerging directive controls this merging if you need finer control.
Nginx blocks the folder but PHP files still answer
Symptom: curl -sI https://example.com/wp-content/upgrade/ returns 403, but wp-content/upgrade/index.php returns 200. Likely cause: the location was written without ^~, so the PHP regex location wins for .php requests. Fix: change it to location ^~ /wp-content/upgrade/, run sudo nginx -t, then reload.
WP-CLI says "Error: Another update is currently in progress."
Likely cause: an earlier update crashed or timed out and left its lock behind. First make sure no update really is running, for example by waiting a few minutes or checking with whoever else manages the site. Then clear the lock, as the WP-CLI docs suggest.
wp option delete core_updater.lockThe checksum check prints warnings
Symptom: lines like Warning: File doesn't verify against checksum: wp-includes/example.php, then Error: WordPress installation doesn't verify against checksums. Likely cause: an interrupted update or a modified core file. Fix: if the update was interrupted, check that no update is still running, run wp core update again and re-run wp core verify-checksums. If warnings remain on files you did not change yourself, treat it as a possible compromise and compare against your backup before going further. Pairing this with the steps in our plugin supply chain audit with WP-CLI gives you a fuller picture.
A few extra layers worth adding
This rule pairs well with turning off the built-in file editor with DISALLOW_FILE_EDIT, which we walk through in our wp-config hardening guide. It also helps to make sure WordPress can reach you when something breaks, so check that your recovery mode email works before a crash.
If you manage the server itself, the web server is only one layer. Keeping PHP and the control panel patched matters just as much, and our guide on how to patch the cPanel PHP RCE with EasyApache shows that routine.
When to call your host: if you are on shared hosting, .htaccess is ignored, and you cannot edit the vhost, ask your host to add the deny rule for you. It takes them a minute.
The one message worth forwarding
Copy this to your developer, host or whoever manages your server:
"Hi, WordPress 7.1.4 (28 October 2026) fixes CVE-2026-119977, an update-window flaw where a full WordPress copy sat at a guessable path under wp-content/upgrade/ during core updates. CISA added it to its Known Exploited Vulnerabilities catalog on 3 November 2026. Could you please confirm our sites are on 7.1.4 or the matching branch release, and add a web server rule that denies all requests to wp-content/upgrade/ (Apache: Require all denied in a Directory block for that folder; Nginx: location ^~ /wp-content/upgrade/ { deny all; })? Then a curl -I on a test file in that folder should return 403. Thanks."
Update first, then block the folder at the web server, because the second step keeps you safe through every update that comes after this one.
Reader questions
Do I still need the server rule if I am already on WordPress 7.1.4?
Yes, it is still worth adding. According to Patchstack, 7.1.4 makes the working folder name much harder to guess and keeps index.php in place, but the folder is still inside your website, and a deny rule removes the exposure completely for every future update.
Will blocking wp-content/upgrade/ break plugin and theme updates?
It should not. WordPress unpacks packages into that folder with PHP directly on the server's disk, not through web requests, so an HTTP deny rule does not touch that process. Run one test update afterwards to confirm on your own setup.
My site has automatic background updates. Am I already safe?
Sites with automatic background updates should receive 7.1.4 on their own, but check the version anyway, because disk or permission problems can stop an update quietly. The update window also returns on every unattended update, which is why the deny rule helps.
Why can a security plugin not block this attack?
A security plugin runs inside your WordPress site, but the copy in wp-content/upgrade/ is a separate WordPress that loads its own files, so your plugin never runs for that request. The block has to happen in Apache or Nginx, before PHP starts.
I run an older branch such as 6.8. Do I need to jump to 7.1.4?
No. WordPress backported the fixes to older branches, for example 6.8.12, 6.9.11 and 7.0.8, and down to 4.7.39 for the 4.7 branch. WordPress 4.6 and earlier get no security updates, so those sites need a major upgrade.
Is CVE-2026-119977 on the CISA Known Exploited Vulnerabilities list?
Yes. CISA added CVE-2026-119977 to its Known Exploited Vulnerabilities catalog on 3 November 2026, with a due date of 6 November 2026 for US federal civilian agencies.
What should normally be inside wp-content/upgrade/?
Between updates it should hold nothing but an index.php file, according to Patchstack. Unknown PHP files in that folder are a reason to investigate before you delete anything.
Was this guide helpful?
Help us make the next update better.
Sources & further reading
- WordPress 7.1.4 release notes, WordPress.org
- Patchstack: WordPress 7.1.4 security release
- webhosting.today: WordPress 7.1.4 fixes 22 flaws
- CISA Known Exploited Vulnerabilities catalog data feed
- WP-CLI: wp core update
- WordPress Advanced Administration: Backups
- Apache HTTP Server: mod_authz_core
- Apache HTTP Server: RewriteRule flags
- nginx: location directive, ngx_http_core_module
- nginx: ngx_http_access_module
Originally published . About our editorial updates.



