# UK social media ban and VPNs: what Ofcom's location signal needs

Ofcom's October 29 assessment names IP address as a signal for VPN use. 93.3% of consumer VPN addresses sit outside the UK. The check, in Express.

Ofcom published its [rapid assessment of highly effective age assurance at 16](https://www.ofcom.org.uk/online-safety/protecting-children/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](https://vpndetection.io/blog/online-safety-act-vpn-detection).

## 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`](https://docs.vpndetection.io/integrations/express), for an Express app behind nginx on the same machine:

```bash
npm init -y
npm pkg set type=module
npm install express vpndetection-express
```

```js
// 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 });
});
```

```js
// 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:

```bash
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](https://mullvad.net/en/servers). Part of the answer the middleware left on the request:

```json
{
  "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](https://check.torproject.org/torbulkexitlist)) 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](https://www.cloudflare.com/ips/) only. The [Express tutorial](https://vpndetection.io/blog/detect-vpn-users-express-js) 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](https://vpndetection.io/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](https://docs.vpndetection.io). With a few million distinct visitors a day, look the VPN and proxy flags up locally in the [VPN IP](https://vpndetection.io/databases/vpn-ip) and [Residential Proxy IP](https://vpndetection.io/databases/residential-proxy-ip) databases instead of calling the [API](https://vpndetection.io/api) per request, and take the country from your own geolocation: neither database has one.

