2026年11月12日
2026年11月12日
Protect WordPress with Google Cloud Armor: Security Policy Setup
This article is a localized edition of the Japanese original. Read the Japanese original
For WordPress on Google Cloud, attaching a Cloud Armor security policy to the backend service of an external Application Load Balancer lets you block brute-force attacks on the login screen, XML-RPC abuse, SQL injection and more before they reach the server. This article explains rule design for WordPress (the order of allow, rate-limit, WAF and default rules), how to create the policy with gcloud commands, how to avoid false positives on the admin screen, and how to verify everything in preview mode.
Because Cloud Armor works in front of the load balancer, it has no effect if you let clients reach a Compute Engine VM directly through its global IP. Your setup must go through the load balancer. A mistake in a rule can also lock out the whole site or the admin screen, so first check the behavior in the preview mode described below before you enable the rules.
Cloud Armor security policy basics
A security policy is a set of rules, each with a "priority (number)," a "condition" and an "action." Requests are evaluated starting from the rule with the smallest priority number, and the action of the first matching rule is applied. If no rule matches, the default rule with the lowest priority (2147483647) is applied.
The main actions are as follows.
- allow / deny: allow the request, or reject it with a status such as 403, 404 or 502.
- throttle: reject only the requests that exceed the number allowed within a set period.
- rate-based-ban: block a source that exceeds the threshold entirely for a set period.
- redirect: send the visitor to a reCAPTCHA verification page or similar (additional setup is required).
For conditions, you can use source IP ranges, expressions using request.path or request.headers (a custom rules language in CEL format), and preconfigured WAF rules based on the OWASP Core Rule Set.
Rule design for WordPress
For WordPress, arranging the rules in the following order makes the intent easy to see and easy to adjust when false positives appear.
| Example priority | Role | Content |
|---|---|---|
| 100 | Allow administrators | Allow /wp-admin/, /wp-login.php and /wp-json/ from a fixed IP at your office or home |
| 200 | Block XML-RPC | Deny /xmlrpc.php if you do not use it |
| 300 | Brute-force protection for login | rate-based-ban on /wp-login.php |
| In the 1000s | WAF | Preconfigured rules for SQL injection, XSS and so on |
| 2147483647 | Default | Allow |
The allow rule for administrator IPs goes before the WAF because the WordPress admin screen saves content that includes HTML and JavaScript, which WAF rules easily judge to be an attack. If you do not have a fixed IP, also consider sending access to the admin screen alone through a different route (for example through Identity-Aware Proxy or a VPN).
Step 1: Create the policy and attach it to the backend
The following commands are examples. Replace the policy name and backend service name with your own, and run them in an environment where gcloud is available, such as Cloud Shell.
# Create the security policy
gcloud compute security-policies create wp-policy \
--description="WordPress security policy"
# Apply it to the backend service of the external Application Load Balancer
gcloud compute backend-services update wp-backend \
--security-policy=wp-policy \
--global
A newly created policy contains only the default "allow all" rule, so the site's behavior does not change at this point.
Step 2: Allow administrator IPs and block XML-RPC
# Give priority to allowing access to the admin screen, login and REST API from the administrator's fixed IP (203.0.113.10 is an example)
gcloud compute security-policies rules create 100 \
--security-policy=wp-policy \
--expression="inIpRange(origin.ip, '203.0.113.10/32') && (request.path.startsWith('/wp-admin/') || request.path.startsWith('/wp-login.php') || request.path.startsWith('/wp-json/'))" \
--action=allow \
--description="admin office IP"
# Deny XML-RPC if you are not using it
gcloud compute security-policies rules create 200 \
--security-policy=wp-policy \
--expression="request.path.startsWith('/xmlrpc.php')" \
--action=deny-403 \
--description="block xmlrpc"
XML-RPC is sometimes used by Jetpack, some smartphone apps and external blog posting tools. Before you block it, check that you are not using any such integrations.
Step 3: Rate-limit the login screen
# Allow up to 10 POSTs to /wp-login.php per 60 seconds from the same IP, and block for 10 minutes if exceeded
gcloud compute security-policies rules create 300 \
--security-policy=wp-policy \
--expression="request.path.startsWith('/wp-login.php') && request.method == 'POST'" \
--action=rate-based-ban \
--rate-limit-threshold-count=10 \
--rate-limit-threshold-interval-sec=60 \
--ban-duration-sec=600 \
--conform-action=allow \
--exceed-action=deny-429 \
--enforce-on-key=IP
Set the threshold so that a legitimate user who mistypes a password a few times is not blocked. If many users log in from the same corporate network (the same IP), such as on a membership site, consider raising the value or using a key other than IP. As a rule, do not rely on Cloud Armor alone to protect the login screen; combine it with strong passwords, two-factor authentication and login attempt limits on the WordPress side.
Step 4: Add preconfigured WAF rules
# SQL injection protection (start with sensitivity 1)
gcloud compute security-policies rules create 1000 \
--security-policy=wp-policy \
--expression="evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1})" \
--action=deny-403 \
--preview
# Cross-site scripting protection
gcloud compute security-policies rules create 1010 \
--security-policy=wp-policy \
--expression="evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 1})" \
--action=deny-403 \
--preview
The higher the sensitivity number, the more signatures are enabled, which widens detection but also increases false positives. For WordPress, the safe approach is to start at sensitivity 1 and raise it while watching the logs. The version included in a rule name (v33 is Core Rule Set 3.3) can be updated (as of October 2026), so check the official documentation for the available rule names before you specify one. Rule sets for local file inclusion (lfi), remote code execution (rce), scanner detection and more are also available.
Step 5: Check for false positives with preview mode and logs
The --preview flag added in step 4 is a mode in which a matching rule does not actually block the request but records in the log that it "would have blocked" it. Run in this state for a few days and check that normal browsing, comment posting, editing in the admin screen and plugin behavior do not produce false positives.
# Set the policy's log level to verbose (when investigating false positives)
gcloud compute security-policies update wp-policy --log-level=VERBOSE
You can view the logs in Cloud Logging as load balancer request logs. They record which rule matched and which signature fired, so if there is a false positive, handle it in one of the following ways.
- For false positives on the admin screen or REST API (the block editor saves through
/wp-json/), let those requests through first with the administrator IP allow rule. - If only a specific signature produces false positives, exclude that signature with
opt_out_rule_ids. - If a specific parameter alone is the cause, use the request field exclusion settings.
If there are no problems, remove the preview and enable the rule.
gcloud compute security-policies rules update 1000 \
--security-policy=wp-policy \
--no-preview
Settings to make on the WordPress side as well
When traffic goes through the load balancer, the source IP seen by WordPress is the load balancer's. To handle the real visitor's IP correctly in access logs and security plugins, configure them to refer to the X-Forwarded-For header. This header can be spoofed, so it is a prerequisite to close, with a firewall, any route that reaches the VM directly from anywhere other than the load balancer. Set up VPC firewall rules that allow only the source IP ranges of Google Cloud load balancers and health checks.
Also, if HTTPS is terminated at the load balancer, WordPress may not recognize that it is being served over HTTPS, and redirects can repeat endlessly. In that case, add code to wp-config.php that looks at X-Forwarded-Proto and treats the request as HTTPS.
// wp-config.php (write this above the "That's all, stop editing!" line)
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
$_SERVER['HTTPS'] = 'on';
}
Even if you make a Cloud Armor rule too strict by mistake and lock yourself out of the admin screen, you can undo it by deleting the rule or switching it to preview from gcloud or the Google Cloud console. Before changing rules, exporting the current settings to a file with gcloud compute security-policies export makes comparing and restoring easier.
Summary
- Use Cloud Armor by attaching it to the backend service of an external Application Load Balancer. Keep direct access to the VM closed.
- Arranging rules in the order "allow administrators → block XML-RPC → rate-limit login → WAF → default allow" makes them easier to manage.
- Keep the sensitivity of preconfigured WAF rules low, and enable them only after checking for false positives with
--previewand the logs. - Saving in the block editor (REST API) and admin screen operations are easily flagged as false positives, so adjust with allow rules and exclusion settings.
- Also make the WordPress-side settings that come with going through a load balancer, such as the source IP and HTTPS detection.
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)