Cloudflare Workers Static IP: What Works
Cloudflare Workers cannot send `fetch` through an HTTP or SOCKS5 proxy, so Fixedmark cannot give a Worker a static IP directly. The pattern that works is a small relay on Railway, Render, Fly.io, or Cloud Run: the Worker calls the relay, and the relay calls the partner through your dedicated IP pair.
Why Workers have no fixed IP, and why a proxy does not help
Workers run in every Cloudflare data center. Subrequests leave from Cloudflare's own network and the address depends on where the Worker ran.
Other runtimes fix this with a proxy setting on the HTTP client. Workers' fetch has no such setting: you cannot pass an agent, a dispatcher, or a proxy URL. Workers do offer outbound TCP through connect(), which can upgrade a plain socket to TLS once with startTls(). That rules out the TLS-wrapped HTTPS proxy, which needs TLS to the proxy and then a second TLS session to the destination inside it. A SOCKS5 handshake over a plain socket followed by startTls() fits the API in principle, but you would write HTTP/1.1 by hand and send proxy credentials unencrypted. We have not tested it and do not support it at launch.
Cloudflare's own options
Cloudflare's Dedicated CDN Egress IPs are an Enterprise add-on with no public price. Per Cloudflare's docs, Workers fetch to services on your origin uses them, and connect() does not. Cloudflare Gateway also has dedicated egress IPs on Enterprise, and the Workers VPC beta can send Worker traffic to the internet through Gateway. Cloudflare does not document whether that path uses Gateway's dedicated egress IPs, so confirm with Cloudflare before you rely on it. There is no self-serve static egress setting for Workers.
Sources for Cloudflare Workers pricing
Checked October 2026. Confirm current details on the vendor's site before you decide. Prices and limits change.
- Cloudflare docs: Dedicated CDN Egress IPs with other products
- Cloudflare docs: Workers VPC
- Cloudflare docs: TCP sockets in Workers
- Cloudflare docs: Workers fetch API
Set up a static IP on Cloudflare Workers
The relay is a few dozen lines. Keep it narrow: it forwards fixed paths to one partner and requires a shared secret, so it never becomes an open proxy.
Fixedmark is in early access. Proxy URLs are issued at launch. Join early access to get yours when your region opens.
Before you start
- A Worker and Wrangler, logged in.
- An account on a platform that can run a small Node.js service: Railway, Render, Fly.io, or Cloud Run.
- A Fixedmark proxy URL (
https://APP_ID:TOKEN@mum.egress.fixedmark.com:443) and the two IPs of your pair. Both are issued at launch. Join early access to get them. - Access to the partner's or database's allowlist, or a contact who can add two IPv4 addresses for you.
Steps
- 1
- 2Store the relay URL and secret in the WorkerRun
npx wrangler secret put RELAY_SECRET. SetRELAY_URLunder[vars]inwrangler.toml, or undervarsinwrangler.jsonc. - 3Call the relay from the WorkerReplace direct calls to the partner with calls to the relay. The partner sees only your Fixedmark pair.
- 4Lock down the relayAllow only the paths you need, and add a Fixedmark destination allowlist so the token can reach only the partner's host.
// The Worker cannot set a proxy on fetch, so it calls a relay you run
// on a platform that can. The relay holds the Fixedmark proxy URL.
export default {
async fetch(request, env) {
const res = await fetch(`${env.RELAY_URL}/v1/orders`, {
headers: { authorization: `Bearer ${env.RELAY_SECRET}` },
});
return new Response(res.body, { status: res.status });
},
};// Deploy on Railway, Render, Fly.io, or Cloud Run.
import { createServer } from "node:http";
import { fetch, ProxyAgent } from "undici";
const proxy = new ProxyAgent(process.env.FIXEDMARK_PROXY_URL);
const UPSTREAM = "https://api.partner.example";
createServer(async (req, res) => {
if (req.headers.authorization !== `Bearer ${process.env.RELAY_SECRET}`) {
res.writeHead(401).end();
return;
}
// Only forward a fixed set of paths. Never build an open proxy.
if (req.url !== "/v1/orders") {
res.writeHead(404).end();
return;
}
try {
const upstream = await fetch(UPSTREAM + req.url, { dispatcher: proxy });
res.writeHead(upstream.status, { "content-type": "application/json" });
res.end(await upstream.text());
} catch {
res.writeHead(502).end();
}
}).listen(process.env.PORT ?? 8080);The relay adds a second hop, so put it and the Fixedmark region close to the partner API, not close to your users.
Verify the egress IP on Workers
Check the IP before you send it to a partner. The IP that matters is the relay's. Run curl from the relay's shell, or add a temporary path to the relay that returns the proxied IP. The endpoint https://fixedmark.com/api/ip returns the caller's IP as plain text, and the What's my IP tool shows the same thing in a browser.
The proxied call should print one of your two Fixedmark IPs every time. The direct call should print a different address. If both print the same platform IP, the client is not using the proxy.
# From the relay's shell: the relay's egress through Fixedmark.
curl -sS --proxy "$FIXEDMARK_PROXY_URL" https://fixedmark.com/api/ip
# From anywhere: the IP a Worker uses without the relay.
# Deploy a Worker that returns (await fetch("https://fixedmark.com/api/ip")).text()
# and compare. It is a Cloudflare address and will not match your pair.// A temporary route or script that reports the egress IP.
// Remove it after you have checked.
import { fetch, ProxyAgent } from "undici";
const proxy = new ProxyAgent(process.env.FIXEDMARK_PROXY_URL);
const viaProxy = await fetch("https://fixedmark.com/api/ip", { dispatcher: proxy });
const direct = await fetch("https://fixedmark.com/api/ip");
console.log({
fixedmark: (await viaProxy.text()).trim(),
platform: (await direct.text()).trim(),
});Databases from Workers
Workers reach databases over connect() TCP sockets or through Hyperdrive. Neither path can go through Fixedmark at launch. For an IP-restricted database, run the queries in the relay service, which can use SOCKS5 or bm tunnel. See Postgres and MySQL allowlists.
Common errors and fixes
The relay returns 401
The Worker's RELAY_SECRET does not match the relay's. Set the same value in both places, then redeploy the Worker.
The relay returns 502
The relay could not reach the partner through the proxy. Run the curl check from the relay to see whether the proxy or the partner refused the connection.
407 Proxy Authentication Required
The proxy did not get valid credentials. Check that the env var holds the full URL with APP_ID:TOKEN@, with no quotes or trailing newline. If the token contains @, :, or /, URL-encode it. Some clients drop credentials from the URL: pass them separately (Deno's basicAuth, axios proxy.auth) or switch to undici's ProxyAgent, which reads them from the URL.
TLS errors to the proxy: wrong version number, EPROTO, Proxy CONNECT aborted, or unable to connect to proxy
The client does not support an https:// proxy URL and is speaking plain HTTP to port 443. Upgrade it: curl 7.52+, urllib3 1.26+ (requests), httpx 0.26+, undici ProxyAgent, Go 1.10+. In Ruby, use Typhoeus: Net::HTTP and Faraday's default adapter cannot tunnel HTTPS through a TLS proxy. For older axios releases, set proxy: false and pass https-proxy-agent as httpsAgent. Java's built-in HttpClient cannot speak TLS to a proxy at all. If you cannot upgrade, use the SOCKS5 URL.
ETIMEDOUT or ECONNRESET to the partner, or a 403 from the partner
The proxy connected, but the destination dropped you. Usually only one IP of the pair is on the allowlist, or the partner has not applied the change yet. Add both IPs. If a destination allowlist is set on your Fixedmark token, check the host and port are on it.
The IP check prints the platform's IP, not a Fixedmark IP
The request went direct. Node's built-in fetch ignores HTTPS_PROXY unless NODE_USE_ENV_PROXY=1 is set (Node 22.21+ and 24+). Many SDKs ignore proxy env vars too. Pass the proxy to the client explicitly, as in the snippets above.
429 or 407 with an X-Proxy-Error: quota_exceeded header
Planned behavior when you pass your plan's soft limit. Check usage in the dashboard or raise the plan.
Database driver has no proxy option
node-postgres, mysql2, psycopg, and most ORMs cannot use an HTTP proxy. Use a SOCKS5-capable driver with FIXEDMARK_SOCKS_URL, or run bm tunnel where the platform allows a second process.
Gotchas on Workers
We do not support Workers directly at launch. If Cloudflare adds a proxy option to fetch, we will document it here.
connect()cannot reach Cloudflare IP ranges or port 25, so a relay behind Cloudflare is reached withfetch, not sockets.- Keep the relay's paths fixed. A relay that forwards any URL is an open proxy on your Fixedmark IPs.
Related guides
- IntegrationRailwayA dedicated IP pair for Railway services when shared static IPs are not enough.
- IntegrationFly.ioFly.io's native egress IPs are cheap. Here is when a portable pair still helps.
- Use casePartner API allowlistingGive Salesforce, SOAP vendors, and payment gateways one dedicated IP pair to allowlist, wherever your code runs.
- IntegrationSupabase Edge FunctionsFixed outbound IPs for Supabase and Lovable Edge Functions with Deno's proxy client.
Frequently asked questions
Can Cloudflare Workers use an HTTP proxy?
No. The Workers fetch API has no option to set a proxy, agent, or dispatcher. Outbound requests always leave from Cloudflare's network.
How do I get a static outbound IP for a Cloudflare Worker?
Call a small relay service that runs on a platform supporting proxies, and have the relay use a static IP proxy. On Enterprise, ask Cloudflare whether Dedicated CDN Egress IPs or Gateway egress IPs cover your Worker's traffic.
Can a Worker use a SOCKS5 proxy with connect()?
In principle, a Worker can do a SOCKS5 handshake on a plain socket and then call startTls. You would write HTTP by hand and send proxy credentials in clear text. Fixedmark does not support this path at launch.
Fix your egress IP.
Fixedmark is in early access. Join the list to get a dedicated IP pair for Cloudflare Workers when your region opens.