Cloud Run Static Outbound IP Without Cloud NAT
Cloud Run services and jobs send traffic from Google-owned, changing addresses. Google's native fix routes egress into a VPC network and out through Cloud NAT with a reserved static IP. A Fixedmark proxy URL gives the same service a dedicated IP pair without VPC egress, a router, or NAT.
Why Cloud Run's outbound IP changes
By default Cloud Run sends outbound traffic straight to the internet from Google's shared infrastructure. The address is not fixed and is shared with other Google Cloud customers.
The native route: Direct VPC egress and Cloud NAT
Google documents a static outbound IP for Cloud Run in four parts. Create a Cloud Router, reserve a regional static external IP, create a Cloud NAT gateway that uses it, and deploy the service with --vpc-egress=all-traffic through Direct VPC egress (recommended) or a Serverless VPC Access connector.
Per Google's Cloud NAT pricing, a public NAT gateway costs $0.0014 per hour for each VM instance using it, up to 32 instances, then a flat $0.044 per hour. Data processed costs $0.045 per GiB, and each NAT IP costs $0.005 per hour. If you already run Cloud Run inside a VPC for Cloud SQL private IP or Memorystore, adding Cloud NAT is a natural extension. If the only goal is one allowlisted partner, the proxy keeps your network config unchanged.
Sources for Google Cloud Run pricing
Checked October 2026. Confirm current details on the vendor's site before you decide. Prices and limits change.
- Google Cloud: Static outbound IP address for Cloud Run
- Google Cloud: Cloud NAT pricing
- Google Cloud: Secrets in Cloud Run
Set up a static IP on Google Cloud Run
Cloud Run containers can run a second process or a sidecar container, so API calls, SOCKS5, and bm tunnel all work.
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 Cloud Run service or job, the
gcloudCLI, and the Secret Manager API enabled. - 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. - For databases: the SOCKS5 URL (
socks5h://APP_ID:TOKEN@mum.egress.fixedmark.com:1080) or thebm tunnelCLI. - Access to the partner's or database's allowlist, or a contact who can add two IPv4 addresses for you.
Steps
- 1Store the proxy URL in Secret ManagerRun
printf %s "$FIXEDMARK_PROXY_URL" | gcloud secrets create fixedmark-proxy --data-file=-and grant the service's runtime service accountroles/secretmanager.secretAccessoron the secret. - 2Expose it as an environment variableDeploy with
gcloud run deploy my-service --set-secrets=FIXEDMARK_PROXY_URL=fixedmark-proxy:1. Jobs use the same flag ongcloud run jobs deploy. Google recommends a pinned version overlatestfor env vars. - 3Configure the HTTP clientPass the proxy URL to the client that calls the allowlisted API, using the snippet for your runtime.
- 4Verify and allowlistRun the IP check below from a request handler or a job execution, confirm the IP, then send both IPs to the partner.
// npm install undici (undici 8 needs Node.js 22.19+)
import { fetch, ProxyAgent } from "undici";
// FIXEDMARK_PROXY_URL=https://APP_ID:TOKEN@mum.egress.fixedmark.com:443
const proxy = new ProxyAgent(process.env.FIXEDMARK_PROXY_URL);
const res = await fetch("https://api.partner.example/v1/orders", {
dispatcher: proxy,
headers: { authorization: `Bearer ${process.env.PARTNER_API_KEY}` },
});
console.log(res.status, await res.json());# pip install requests (urllib3 1.26+ for an https:// proxy URL)
import os
import requests
proxy = os.environ["FIXEDMARK_PROXY_URL"]
proxies = {"http": proxy, "https": proxy}
res = requests.get(
"https://api.partner.example/v1/orders",
proxies=proxies,
headers={"Authorization": f"Bearer {os.environ['PARTNER_API_KEY']}"},
timeout=15,
)
print(res.status_code, res.json())package main
import (
"fmt"
"net/http"
"net/url"
"os"
"time"
)
func main() {
proxyURL, err := url.Parse(os.Getenv("FIXEDMARK_PROXY_URL"))
if err != nil {
panic(err)
}
client := &http.Client{
Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
Timeout: 15 * time.Second,
}
res, err := client.Get("https://api.partner.example/v1/orders")
if err != nil {
panic(err)
}
defer res.Body.Close()
fmt.Println(res.Status)
}SDKs that do not use fetch (the Stripe and Salesforce Node SDKs, got, axios) accept an HTTP agent. Pass new HttpsProxyAgent(process.env.FIXEDMARK_PROXY_URL) from the https-proxy-agent package, which supports https:// proxy URLs. With axios, also set proxy: false so it does not apply its own proxy logic. undici 8 needs Node 22.19 or later. On older Node, install undici@7.
Verify the egress IP on Cloud Run
Check the IP before you send it to a partner. The quickest check is a one-off job: gcloud run jobs deploy egress-check --image curlimages/curl --set-secrets=FIXEDMARK_PROXY_URL=fixedmark-proxy:1 --command sh --args=-c,'curl -sS --proxy "$FIXEDMARK_PROXY_URL" https://fixedmark.com/api/ip' --execute-now, then read the job's logs. 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.
# Needs curl 7.52 or later for an https:// proxy URL.
# Prints the IP the destination sees. Run it a few times:
# you should only ever see the two IPs of your pair.
curl -sS --proxy "$FIXEDMARK_PROXY_URL" https://fixedmark.com/api/ip
# Same check over SOCKS5, the path database drivers use.
curl -sS --proxy "$FIXEDMARK_SOCKS_URL" https://fixedmark.com/api/ip
# Without the proxy, for comparison. This is the platform's own IP.
curl -sS https://fixedmark.com/api/ip// 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(),
});import os
import requests
proxy = os.environ["FIXEDMARK_PROXY_URL"]
via = requests.get("https://fixedmark.com/api/ip", proxies={"https": proxy}, timeout=10)
direct = requests.get("https://fixedmark.com/api/ip", timeout=10)
print({"fixedmark": via.text.strip(), "platform": direct.text.strip()})Databases: Postgres, MySQL, and MongoDB
Database drivers do not speak HTTP CONNECT, so the proxy URL above does not cover them. On Cloud Run you have two options. Drivers with SOCKS5 support can use FIXEDMARK_SOCKS_URL directly: the MongoDB Node.js driver accepts proxyHost and proxyPort options, and Go's pgx accepts a SOCKS5 dialer. For any other driver, run the bm tunnel CLI (planned for launch) as a background process in the container entrypoint or a sidecar container (use instance-based billing, --no-cpu-throttling, so it is not throttled between requests), then point the driver at 127.0.0.1.
TLS to the database stays end to end. Fixedmark forwards encrypted bytes and never sees your queries. If your driver verifies the server certificate, keep the real database hostname as the TLS server name when you connect through the local tunnel.
- MongoDB Atlas: add both Fixedmark IPs to the project's IP Access List. See MongoDB Atlas allowlisting.
- AWS RDS, Cloud SQL, and self-hosted Postgres or MySQL: add both IPs as /32 entries. See Postgres and MySQL allowlists.
# Forward local port 5432 to the database through your Fixedmark IPs.
# bm tunnel is planned for launch; flags are not final.
bm tunnel 5432:db.example.com:5432 &
# Point the driver at the local end of the tunnel.
DATABASE_URL="postgres://app:secret@127.0.0.1:5432/app?sslmode=require"// npm install mongodb socks
import { MongoClient } from "mongodb";
// FIXEDMARK_SOCKS_URL=socks5h://APP_ID:TOKEN@mum.egress.fixedmark.com:1080
const socks = new URL(process.env.FIXEDMARK_SOCKS_URL);
const client = new MongoClient(process.env.MONGODB_URI, {
proxyHost: socks.hostname,
proxyPort: Number(socks.port),
proxyUsername: decodeURIComponent(socks.username),
proxyPassword: decodeURIComponent(socks.password),
});
await client.connect();import (
"context"
"net/url"
"os"
"github.com/jackc/pgx/v5/pgxpool"
"golang.org/x/net/proxy"
)
func connect(ctx context.Context) (*pgxpool.Pool, error) {
u, err := url.Parse(os.Getenv("FIXEDMARK_SOCKS_URL"))
if err != nil {
return nil, err
}
pw, _ := u.User.Password()
dialer, err := proxy.SOCKS5("tcp", u.Host,
&proxy.Auth{User: u.User.Username(), Password: pw}, proxy.Direct)
if err != nil {
return nil, err
}
cfg, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
if err != nil {
return nil, err
}
cfg.ConnConfig.DialFunc = dialer.(proxy.ContextDialer).DialContext
return pgxpool.NewWithConfig(ctx, cfg)
}Common errors and fixes
Permission denied on secret at deploy time
The runtime service account cannot read the secret. Grant it roles/secretmanager.secretAccessor on fixedmark-proxy.
Google API calls fail or show up in Fixedmark logs
You set HTTPS_PROXY for the whole container. Google's Node.js client libraries honor it. Remove it and pass FIXEDMARK_PROXY_URL explicitly.
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 Cloud Run
Do not set HTTPS_PROXY for the whole container. Google client libraries would send Google API calls through the proxy too. Use FIXEDMARK_PROXY_URL and pass it explicitly.
- With request-based billing, CPU is throttled between requests. Long-lived tunnels and background calls need instance-based billing.
- If the service already uses
--vpc-egress=all-trafficwith Cloud NAT, the proxy still works. The partner sees the Fixedmark pair for proxied calls.
Related guides
- ComparisonGoogle Cloud NATWhat a static outbound IP through Cloud NAT costs and involves on Google Cloud, and when a proxy is simpler.
- IntegrationAWS LambdaA fixed IP for Lambda without VPC, subnets, and two NAT Gateways.
- Use casePostgres and MySQLAllowlist two IPs on RDS, Cloud SQL, or a self-hosted database and connect through SOCKS5 or a local tunnel.
- Use caseBank and UPI APIsWhitelist one dedicated IP pair with your bank, UPI partner bank, or GSP, then deploy on Vercel or Railway without re-whitelisting.
Frequently asked questions
How do I get a static outbound IP on Cloud Run?
Google's method routes egress through a VPC with Direct VPC egress or a connector, then out through Cloud NAT with a reserved static IP. Alternatively, route the calls that need it through a static IP proxy and leave networking unchanged.
How much does Cloud NAT cost for Cloud Run?
Per Google's pricing, $0.0014 per hour per instance using the gateway (capped at $0.044 per hour above 32), plus $0.045 per GiB processed and $0.005 per hour per NAT IP.
Does the proxy work for Cloud Run jobs?
Yes. Jobs read secrets and environment variables the same way services do, and the same client code applies.
Can I reach Cloud SQL through Fixedmark?
You can reach a Cloud SQL public IP that has authorized networks by allowlisting the Fixedmark pair and connecting via SOCKS5 or the tunnel. For private IP, use Google's VPC connectivity instead.
Fix your egress IP.
Fixedmark is in early access. Join the list to get a dedicated IP pair for Google Cloud Run when your region opens.