Skip to Content
Framework GuidesThe visitor's IP

The visitor’s IP

Every request that reaches your app through Ghayma carries the visitor’s IP address in the X-Real-IP header. Read that header, not the connection’s address: the connection comes from Ghayma’s edge.

HeaderHolds
X-Real-IPThe visitor’s IP. Read this one.
X-Ghayma-Client-IP, CF-Connecting-IP, True-Client-IPThe same IP
X-Forwarded-ForThe same IP, as a single entry

This holds on every route: your *.ghayma.app address through Cloudflare, and a custom domain pointed straight at Ghayma.

It cannot be faked

Ghayma’s edge deletes every IP header a visitor sends, under any spelling, before it writes its own. A visitor who sends X-Real-IP: 1.2.3.4 or X-Forwarded-For: 1.2.3.4 never gets that value to your app, so X-Real-IP is safe for rate limits, fraud checks and audit logs.

This guarantee comes from Ghayma’s edge. If your app is also reachable another way — a different host, a tunnel, your own proxy in front of it — X-Real-IP is only as trustworthy as that path: make sure whatever sits in front of your app overwrites it, or don’t rely on it there.

Read it

Next.js — route handler
export async function GET(request: Request) { const ip = request.headers.get("x-real-ip"); return Response.json({ ip }); }
Next.js — server component or action
import { headers } from "next/headers"; export default async function Page() { const ip = (await headers()).get("x-real-ip"); // … }
@ghayma/sdk 1.6.0+
import { getClientIp } from "@ghayma/sdk"; const ip = getClientIp(request); // a Request, a Headers, or a Node req
Express
app.get("/", (req, res) => { const ip = req.get("x-real-ip"); res.json({ ip }); });
Node (http)
const ip = req.headers["x-real-ip"];
PHP
$ip = $_SERVER['HTTP_X_REAL_IP'] ?? null; // plain PHP $ip = $request->header('X-Real-IP'); // Laravel
Python (Flask, FastAPI, Django)
ip = request.headers.get("X-Real-IP")
Go
ip := r.Header.Get("X-Real-IP")
Dart (dart:io)
final ip = request.headers.value('x-real-ip');

Framework helpers such as Express’s req.ip or Laravel’s $request->ip() return Ghayma’s edge unless you configure them to trust a proxy. Read the header instead.

A rate limit per visitor

app/api/contact/route.ts
const hits = new Map<string, { count: number; until: number }>(); export async function POST(request: Request) { const ip = request.headers.get("x-real-ip") ?? "unknown"; const now = Date.now(); const entry = hits.get(ip); if (entry && entry.until > now && entry.count >= 5) { return new Response("Too many requests", { status: 429 }); } hits.set(ip, entry && entry.until > now ? { ...entry, count: entry.count + 1 } : { count: 1, until: now + 60_000 }); // … handle the request return Response.json({ ok: true }); }

This map lives in one instance’s memory; with several replicas, keep the counters in a shared store such as a Valkey database of your project.

Forwarding it to Ghayma Auth

When your server calls the auth service for a user, pass this IP as clientIp (with the app’s server key), so the auth service counts its rate limits per user and not per server. See Auth integration.

Calls from your other apps

A request from another app running on Ghayma carries that app’s own address (a 10.x address), not a visitor’s. Requests from browsers and from servers outside Ghayma always arrive with the visitor’s IP.