Real visitor IP
Every request to a public HTTP project goes through Zenifra first, which handles HTTPS, DDoS protection, and routing to your application. Because of that, the connection address your application receives is an internal platform address, not the address of whoever made the request. The visitor's real IP arrives in an HTTP header.
Where the visitor IP is
| Source | What it contains | Use it? |
|---|---|---|
X-Real-IP header | The visitor IP, set by Zenifra | Yes |
X-Forwarded-For header | A list of addresses. The last one is the visitor IP; earlier ones may have been sent by the visitor | Only the last item |
Connection address (req.socket.remoteAddress, REMOTE_ADDR) | An internal platform address, the same for every visitor | No |
Zenifra always overwrites X-Real-IP. Even if the visitor sends this header with another value, your application receives the IP the request actually came from.
Do not use the first X-Forwarded-For item
Visitors can send their own X-Forwarded-For. Zenifra keeps what they sent and appends the real IP at the end:
X-Forwarded-For: 1.2.3.4, 203.0.113.10
X-Real-IP: 203.0.113.10In this example, 1.2.3.4 was made up by the visitor and 203.0.113.10 is the real IP. Code that takes the first item of the list can be fooled with a header, which breaks rate limits, blocks, and audit logs. Prefer X-Real-IP.
Examples by language
The header does not exist in local development, so the examples fall back to the connection address.
Node.js with Express
// Trust one proxy hop: Zenifra's
app.set('trust proxy', 1)
app.get('/', (req, res) => {
res.send(`Your IP: ${req.ip}`)
})With trust proxy set to 1, req.ip uses the last X-Forwarded-For item, which is the real IP. Do not set trust proxy to true: in that case Express uses the first item, which the visitor can make up.
Node.js without a framework
const ip = req.headers['x-real-ip'] ?? req.socket.remoteAddressPython
# Flask
ip = request.headers.get('X-Real-IP', request.remote_addr)
# FastAPI
ip = request.headers.get('x-real-ip', request.client.host)
# Django
ip = request.META.get('HTTP_X_REAL_IP', request.META.get('REMOTE_ADDR'))PHP
$ip = $_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'];Go
ip := r.Header.Get("X-Real-IP")
if ip == "" {
ip, _, _ = net.SplitHostPort(r.RemoteAddr)
}Original HTTPS and port
The application receives the request over an internal connection, but Zenifra tells it how the visitor connected:
X-Forwarded-Proto: https
X-Forwarded-Port: 443Like X-Real-IP, these values are set by Zenifra and cannot be changed by the visitor. When the proxy is trusted, frameworks recognize the HTTPS access and generate absolute URLs with https:// and Secure cookies:
- Express: with
app.set('trust proxy', 1),req.protocolis'https'andreq.secureistrue. - Django:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')makesrequest.is_secure()returnTrue.
Best practices
- Store the IP as text. Accept IPv4 and IPv6, up to 45 characters.
- Use the IP for rate limits, logs, and fraud checks, always from
X-Real-IP. - Do not force HTTPS in the application. Zenifra already redirects
http://tohttps://before the request arrives. Options such asSECURE_SSL_REDIRECTin Django,force_sslin Rails, or redirect middlewares can create a loop. - To block or allow IPs, prefer the console's IP access control (from the Premium Plus plan). It uses the real visitor IP and stops the request before it reaches your application.
- An IP is not an identity. Many people can share the same IP, such as in companies and mobile networks. Keep login, tokens, or API keys on sensitive routes.
Next steps
FAQ
Why does my application show the same IP for every visitor?
It is reading the connection address, which is internal to the platform. Switch to the X-Real-IP header, as in the examples above.
Can a visitor fake X-Real-IP?
No. Zenifra replaces the value sent by the visitor with the IP the request came from.
Does it work with a custom domain?
Yes. The header arrives the same way on the Zenifra subdomain and on custom domains.
Last updated on
Network and access control
Choose between a public or private project, understand the Zenifra subdomain, and restrict access by IP with CIDR whitelist and blacklist rules.
Custom domains
Configure apex domains, subdomains, CNAME records, TXT validation records, and HTTPS to point your own domain to an application hosted on Zenifra.