Notes
The IP address on your WooCommerce order may not be the customer’s
WooCommerce reads the shopper's address from a forwarded header. Behind a proxy that is either the proxy itself or a value the client chose, and both break rules.
6 min read
You find the address that placed forty failed orders overnight, and you block it. The next morning the same run arrives from somewhere else, and a genuine customer emails to say your checkout will not let them buy anything. Both outcomes come from the same misunderstanding about the WooCommerce customer IP address, and it is worth clearing up before you block anything at all.
The address stored on a WooCommerce order is not read from the network. It comes from a header, and whoever is talking to your server writes that header. Behind a proxy, the address on the order is whatever your server was told to believe.
Where the WooCommerce customer IP address comes from
The WooCommerce customer IP address lands on the order at the moment the order appears, and nothing revisits it. When the classic checkout builds an order it stamps the shopper's details onto it directly. set_customer_ip_address() takes its value from WooCommerce's own geolocation helper. set_customer_user_agent() stores the browser string alongside it. That helper is where the interesting part happens.
WooCommerce checks three things in order. First it looks for the X-Real-IP header and, if it is present, returns it as it stands. Failing that it looks for X-Forwarded-For. The comment in the source explains the reasoning: "Proxy servers can send through this header like this: X-Forwarded-For: client1, proxy1, proxy2". It takes the first entry, because that "should always be the client IP". Only if neither header exists does it fall back to REMOTE_ADDR, the address of whatever actually opened the connection.
That order of preference is a sensible default for a store behind a load balancer. It also means the WooCommerce customer IP address on your order is a claim in a header. The difference matters as soon as you act on it.
The first failure: everyone looks like one visitor
Say your site sits behind Cloudflare, a CDN, a load balancer or a host-level reverse proxy, and nothing forwards the original address. REMOTE_ADDR is then the proxy. Every WooCommerce customer IP address in your store is then one of the same handful, because every request genuinely did come from there.
Nothing looks broken. Orders complete, the field carries a value, and that value is a valid public address. The damage shows up in anything that groups by address. A rate limit meant to slow one attacker now counts every shopper into one bucket. A busy hour then trips a rule written for a bot. A block list aimed at one abusive visitor takes out the proxy, and with it everybody arriving through it. Fraud rules that look for many orders from one address fire constantly and teach you to ignore them.
The second failure: anyone can claim any address
The opposite configuration is worse in a quieter way. Suppose your server accepts a forwarded header without checking who sent it. Suppose too that anything on the internet can reach your origin directly, not only through the proxy. The client then chooses the address on the order.
MDN's reference for the X-Forwarded-For header is unambiguous about the consequences. "If any proxy is malicious or misconfigured, any part of the header not added by a trusted proxy may be spoofed or may have an unexpected format or contents", and "If the server can be directly connected to from the internet, even if it is also behind a trusted reverse proxy, no part of the X-Forwarded-For IP list can be considered trustworthy or safe for security-related uses". It goes further and names the exact uses at risk. "Any security-related use of X-Forwarded-For (such as for rate limiting or IP-based access control) must only use IP addresses added by a trusted proxy."
Read that against a header WooCommerce trusts on sight and the picture is clear enough. A script that sets a fresh address on every request defeats per-address counting entirely. A script that sets your office address inherits whatever your allowlist grants it. The log fills with addresses that were never anywhere near your store.
What a proxy actually sends
Cloudflare documents its own headers, and they are a useful model for any proxy. CF-Connecting-IP "provides the client IP address connecting to Cloudflare to the origin web server". X-Forwarded-For "maintains proxy server and original visitor IP addresses". It matches the connecting address when no such header was present, and appends to the list when one was. There is also True-Client-IP, which serves the same purpose under a different name, on Enterprise plans only. Cloudflare recommends the first or the third over X-Forwarded-For, on the grounds that they hold a consistent format containing only one address.
The single-value point is the practical one. A header that carries a list makes you decide which entry to believe. That decision is the whole security question in miniature.
How cautious core code is about this
WooCommerce's Store API rate limiter is a good illustration, because it treats the same question far more carefully than the geolocation helper does. Rate limiting there ships disabled, and it carries a separate proxy_support option that also ships disabled. The source describes that option as something the user can enable "if the store is behind a proxy, load balancer, CDN etc." to properly obtain the client's address.
With proxy support off, it uses REMOTE_ADDR and nothing else. Only when you switch it on does it consult X-Real-IP, then Client-IP, then X-Forwarded-For, then the standard Forwarded header described by RFC 7239. That is the right shape. Trusting a forwarded header is a deliberate decision. Only the site owner knows what sits in front of the origin.
Finding out what your store actually sees
This is a ten minute job and it settles the argument.
- Place a test order from a connection whose public address you know, then compare that address against the one stored on the order.
- If they differ, ask your host or CDN which header carries the original address, and confirm the server or the site is reading that specific header.
- Check whether anything can reach your origin directly, bypassing the proxy. If so, treat any forwarded header as untrusted input, whatever else you do.
- Send a request to your own site with an invented forwarded header and see whether the invented value turns up in the order or the log. If it does, you have your answer.
- Only then write rules that depend on the address.
Once the WooCommerce customer IP address is trustworthy, keep it in proportion. It is good evidence for grouping a burst of attempts together, which is how a run of card testing gives itself away. It is poor evidence for a permanent block. Households, offices and mobile networks share one address between many people, and the customer you lock out will not write in to tell you.
Where Checkout Bouncer fits
Checkout Bouncer is a free, GPL plugin on WordPress.org . It scores WooCommerce checkout submissions with Google reCAPTCHA v3 and logs what it saw, WooCommerce customer IP address included. Its throttling and its allowlists group by address, so it inherits the problem described here. It does not guess either. Behind Cloudflare or any other reverse proxy you point it at the header your proxy sets, using the checkout_bouncer_trusted_proxy_header filter documented in the setup documentation. Without that, every shopper shares one bucket. The event log stores addresses in anonymised form. It is checkout only, it is not a firewall, and a Pro tier is planned rather than sold, so there is nothing to buy.