On this page
Ofcom published its rapid assessment of highly effective age assurance at 16 on October 29, 2026, the report the government asked for before Parliament debates the under-16 social media ban. It names a visitor's IP address among the "location-related signals" that may help detect VPN use. That signal works only once you know which addresses are VPNs: 93.3% of consumer VPN addresses sat outside the UK on October 31, so a UK teenager on one usually doesn't look British. This post covers what Ofcom said about VPNs, what a location signal needs, and the check in Express. It's about the engineering, not legal advice. For the background, including why the government won't ban VPNs, see our October 7 post.
What Ofcom said about VPNs
Ofcom's preliminary assessment names four methods that can be highly effective at telling whether someone is 16 or over: photo ID matching, digital identity services, banking-based checks and facial age estimation. Age inference isn't one of them. Current methods "cannot act as an age gate", since they need a user's activity on a service over time.
On getting round the checks, the assessment says:
No age assurance method is completely robust against circumvention by children. Social media services will need to implement highly effective age assurance in full, including taking practical measures to tackle circumvention by children.
It names VPNs as one way past: "A user may bypass protections altogether by using VPNs or other similar technologies that mask a user's location given that services may only apply these restrictions to users based in the UK." It doesn't ask services to keep VPN users out: "The use of VPNs in the UK is legal and services are not required to prevent access to their service via the use of VPNs."
Ofcom's own research, from August 2026, found that a quarter of children had used a VPN in the previous six months. 30% of 11- to 17-year-olds in Great Britain had reached content meant for older people in that time, and 12% of them said they had used a VPN to do it.
The assessment lists three measures that may reduce circumvention: age inference, to find under-16s who got past the age check; reports from other users; and this one:
Use of existing location-related signals: location-related signals such as IP address, device and time settings, or GPS information may support anti-circumvention measures by helping detect VPN use or find likely UK users subject to jurisdiction-specific age assurance requirements.
It adds that this "requires careful consideration, as VPNs can be shared across multiple devices and given the potential impacts on people legitimately using VPNs as a privacy-preserving measure", and that a signal read from the device through storage and access technologies needs the user's consent under PECR. Of the three signals, the IP address is the one every request already carries.
A location signal needs a VPN check first
Ofcom's two uses of the signal, detecting VPN use and finding likely UK users, depend on each other. A VPN shows its user wherever its server is, so a UK teenager on a server in Frankfurt has a German address. This is where VPN and proxy addresses sit, from our data on October 31, 2026:
| IPv4 addresses | In the UK | Outside the UK | |
|---|---|---|---|
| VPN, all types | 16,304,115 | 717,255 (4.4%) | 15,586,860 (95.6%) |
| Consumer VPN services | 3,388,426 | 228,012 (6.7%) | 3,160,414 (93.3%) |
| Residential proxies, seen in the last 30 days | 34,992,756 | 700,669 (2.0%) | 34,292,087 (98.0%) |
Consumer VPN services are the ones a person signs up to and picks a server on, so the row leaves out corporate gateways and privacy relays.
Their addresses are in 237 countries. The US holds 782,401 of them and Germany 421,297, and the UK is third with 228,012. 323 consumer VPN services have UK addresses, against 374 with US ones. So the same app makes a UK teenager look British on a UK server and foreign on any other.
Residential proxies mislead differently. A residential proxy exit is someone's home connection, so its location is right about the address and says nothing about who's using it. A UK teenager on an exit in Brazil looks like a household in Brazil.
So the gate asks two questions of every visitor, not one: is this address in the UK, and is it one that may not be where the visitor is? A yes to either sends the visitor to age assurance.
Check every visitor in Express
Here it is with vpndetection-express, for an Express app behind nginx on the same machine:
npm init -y
npm pkg set type=module
npm install express vpndetection-express// app.js
import express from 'express';
import { vpndetection } from 'vpndetection-express';
// Each of these means the address may not be where the visitor is.
const MASKED = [
'isVpn', 'isResproxy', 'isMobproxy', 'isDcproxy', 'isPubproxy', 'isTor',
'isRelay', 'isHosting',
];
export const app = express();
app.set('trust proxy', 'loopback'); // nginx on the same machine
app.use(vpndetection({ apiKey: process.env.VPNDETECTION_API_KEY }));
app.use((req, res, next) => {
const { result, error } = req.vpndetection;
const country = result?.location?.country || null;
const masked = MASKED.filter((flag) => result?.[flag] === true);
// In the UK, maybe not where they appear, or we can't tell: check.
req.checkAge = country === 'GB' || masked.length > 0 || country === null;
console.log(`checkAge=${req.checkAge} country=${country ?? '-'} `
+ `masked=${masked.join(',') || '-'} error=${error?.kind ?? '-'}`);
next();
});
app.post('/signup', (req, res) => {
if (req.checkAge) {
// One of Ofcom's four methods before the account exists.
return res.status(202).json({ next: '/age-check' });
}
res.status(201).json({ created: true });
});// server.js
import { app } from './app.js';
app.listen(3000, '127.0.0.1');The middleware looks up each visitor and leaves the answer on req.vpndetection. The next one decides, logs the decision without the address, and the signup route sends a visitor who needs it to your age assurance flow before the account exists. country is the API's own location.country, the location signal, and it's empty for an address the middleware answers in the process, such as a private one, hence the || null.
We ran it on October 31, 2026, with Express 5.3.0, vpndetection-express 2.0.20 and a key on the Max plan, and sent each visitor's address in X-Forwarded-For from the same machine, as nginx would:
VPNDETECTION_API_KEY=your_key node server.js
curl -s -X POST localhost:3000/signup -H 'X-Forwarded-For: 185.213.155.73'{"next":"/age-check"}
185.213.155.73 is Mullvad's server in Frankfurt, from Mullvad's own server list. Part of the answer the middleware left on the request:
{
"isVpn": true,
"isHosting": true,
"vpn": {
"provider": "mullvad",
"last_seen": "2026-10-31",
"confidence": "high"
},
"location": {
"country": "DE",
"country_name": "Germany"
}
}Then, in order, Mullvad's server in London (141.98.252.67), a home broadband line in the UK, a residential proxy exit on a UK home line, another in Brazil, a Tor exit (185.220.101.53, from the Tor Project's exit list) and a home broadband line in the US. The log:
checkAge=true country=DE masked=isVpn,isHosting error=-
checkAge=true country=GB masked=isVpn,isHosting error=-
checkAge=true country=GB masked=- error=-
checkAge=true country=GB masked=isResproxy error=-
checkAge=true country=BR masked=isResproxy error=-
checkAge=true country=DE masked=isResproxy,isTor,isHosting error=-
checkAge=false country=US masked=- error=-
Only the US home line signed up without an age check. A gate that asked only country === 'GB' would also have let through the Frankfurt server, the proxy exit in Brazil and the Tor exit.
Trust only your own proxy
All of this looks up whatever address Express hands the middleware, and trust proxy decides which one that is. nginx appends the address it saw to X-Forwarded-For, after anything the visitor wrote there. We put nginx 1.29.1 in front, with proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for, and sent a signup from Mullvad's London server's own address (a local Docker network on its range, so nginx saw that address), once plain and once with X-Forwarded-For set to a US home line's address:
trust proxy | Plain | With the forged header |
|---|---|---|
'loopback' | GB, isVpn: age check | GB, isVpn: age check |
true | GB, isVpn: age check | US, nothing flagged: account created |
| not set | no country: age check | no country: age check |
With true, Express takes the left-most entry, the one the visitor wrote, so a VPN user who forges a home address in another country skips the check. Left unset, every visitor is nginx at 127.0.0.1, which the middleware answers in the process without a country. It says so once, on console.warn: "resolved the client address as 127.0.0.1, which is not a public address". That fails safe, but every visitor gets the age check, adults abroad included. Name your proxies: 'loopback' for nginx on the same machine, or your load balancer's subnet. Behind Cloudflare, read CF-Connecting-IP with headerIpSelector and accept connections from Cloudflare's addresses only. The Express tutorial covers each case.
When the lookup fails
With an invalid key, the Frankfurt server and the US home line were both sent to the check, logged country=- masked=- error=unauthorized. The middleware lets a request through when a lookup fails, and the gate reads the unknown country as a reason to check.
Without a key, the API answers is_vpn alone, so country is never known and every visitor is checked. The Frankfurt server came back masked=isVpn, and the UK proxy exit and the US home line masked=-. A free key adds location, the Tor and relay flags, and asn. The proxy and hosting flags need a paid plan (pricing).
What an IP check can't do
12% of the children who reached older content used a VPN, so most didn't. Ofcom lists the other ways: a fake or manipulated photo or video for facial age estimation, false information, an adult completing the check for them, or an adult's account. An IP check sees none of them. The age assurance it sends visitors to has to.
A flag isn't proof of a child either. A residential proxy exit is a household's broadband, a mobile proxy shares its address with the phones on its network, and an adult may use a VPN for privacy, as Ofcom notes. That's why the code sends a flagged visitor to age assurance and never refuses them: an adult in Manchester on a VPN verifies their age and gets in. And a proxy exit we haven't seen yet looks like any other home line, so IP is one signal beside the two others Ofcom names, age inference and user reports.
What comes next
The government has said it intends to lay the first regulations before Parliament by the end of 2026. Ofcom then consults on draft guidance on highly effective age assurance at 16, where it says it will revisit the anti-circumvention measures above. Twelve months after the restrictions come into force, it intends to publish research on circumvention, in consultation with the ICO.
The same middleware exists for Django, Laravel, Rails and other frameworks in the docs. With a few million distinct visitors a day, look the VPN and proxy flags up locally in the VPN IP and Residential Proxy IP databases instead of calling the API per request, and take the country from your own geolocation: neither database has one.


