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.
| Header | Holds |
|---|---|
X-Real-IP | The visitor’s IP. Read this one. |
X-Ghayma-Client-IP, CF-Connecting-IP, True-Client-IP | The same IP |
X-Forwarded-For | The 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
export async function GET(request: Request) {
const ip = request.headers.get("x-real-ip");
return Response.json({ ip });
}import { headers } from "next/headers";
export default async function Page() {
const ip = (await headers()).get("x-real-ip");
// …
}import { getClientIp } from "@ghayma/sdk";
const ip = getClientIp(request); // a Request, a Headers, or a Node reqapp.get("/", (req, res) => {
const ip = req.get("x-real-ip");
res.json({ ip });
});const ip = req.headers["x-real-ip"];$ip = $_SERVER['HTTP_X_REAL_IP'] ?? null; // plain PHP
$ip = $request->header('X-Real-IP'); // Laravelip = request.headers.get("X-Real-IP")ip := r.Header.Get("X-Real-IP")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
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.