GitHub Actions Static IP for Allowlisted Calls
GitHub-hosted runners get a different IP from large shared ranges on every job. To reach an allowlisted server, set `HTTPS_PROXY` from a secret on the specific steps that need it. curl, Python requests, and Go read it automatically, Node does with `NODE_USE_ENV_PROXY=1`, and the server sees only your dedicated Fixedmark pair.
Why GitHub-hosted runners change IP
Each job gets a fresh virtual machine. Linux and Windows runners run in Azure, and GitHub lists their address ranges under the actions key of its meta API. The list covers thousands of ranges shared with every GitHub user, changes weekly, and GitHub itself does not recommend using it as an allowlist.
GitHub's native options
GitHub offers larger runners with static IP address ranges, but only for organizations on GitHub Enterprise Cloud. They are Linux and Windows only, billed per minute at larger-runner rates, with up to 10 static-IP runner pools by default. Azure private networking, available on Team and Enterprise Cloud, runs hosted runners inside your own Azure virtual network, where you control egress. Self-hosted runners on a server with a fixed IP are the third route, at the cost of maintaining that server.
These make sense if most of your CI must originate from a fixed address. If only a deploy step or a nightly sync needs it, scoping a proxy to that step is simpler and keeps standard runners on any plan.
Sources for GitHub Actions pricing
Checked October 2026. Confirm current details on the vendor's site before you decide. Prices and limits change.
- GitHub docs: Manage larger runners (static IPs)
- GitHub docs: GitHub-hosted runners reference
- GitHub docs: Private networking for hosted runners
- Node.js docs: NODE_USE_ENV_PROXY
Set up a static IP on GitHub Actions
Set the proxy on steps, not on the job. That way checkout, caches, and artifact uploads go direct and do not count against your bandwidth.
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 repository with Actions enabled, on any GitHub plan.
- 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. - Admin access to the repository or organization to add secrets.
- Access to the partner's or database's allowlist, or a contact who can add two IPv4 addresses for you.
Steps
- 1Add the repository secretUnder Settings, Secrets and variables, Actions, add
FIXEDMARK_PROXY_URL. Use an environment secret if only production deploys should have it. - 2Set HTTPS_PROXY on the stepIn each step that calls the allowlisted server, add an
enventry that setsHTTPS_PROXYfromsecrets.FIXEDMARK_PROXY_URL. AddNO_PROXYfor local services. - 3Handle Node.js explicitlyNode's built-in
fetchignoresHTTPS_PROXYunlessNODE_USE_ENV_PROXY=1is set. That needs Node 22.21+ or 24+. On older Node, pass the URL to undici'sProxyAgent. - 4Verify in the logRun the curl check in the proxied step. It prints one of your Fixedmark IPs. GitHub masks the secret in logs.
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5 # direct, no proxy
- name: Call allowlisted partner API
env:
HTTPS_PROXY: ${{ secrets.FIXEDMARK_PROXY_URL }}
NO_PROXY: localhost,127.0.0.1
run: |
curl -fsS https://fixedmark.com/api/ip # prints a Fixedmark IP
curl -fsS -H "Authorization: Bearer ${{ secrets.PARTNER_API_KEY }}" \
https://api.partner.example/v1/orders # Python requests and Go net/http read HTTPS_PROXY on their own.
- name: Run sync script
env:
HTTPS_PROXY: ${{ secrets.FIXEDMARK_PROXY_URL }}
run: python scripts/sync.py # or: go run ./cmd/sync
# Node's built-in fetch reads HTTPS_PROXY only when
# NODE_USE_ENV_PROXY=1 is set (Node 22.21+ and 24+).
- name: Run Node script
env:
HTTPS_PROXY: ${{ secrets.FIXEDMARK_PROXY_URL }}
NODE_USE_ENV_PROXY: "1"
run: node scripts/sync.mjs// 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());Verify the egress IP on GitHub Actions
Check the IP before you send it to a partner. Put the curl check at the top of the proxied step. With HTTPS_PROXY set, plain curl https://fixedmark.com/api/ip uses it. 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/ipDatabases from Actions
Migrations against an IP-restricted database are the most common case. Run bm tunnel 5432:db.example.com:5432 & (planned for launch) in a step, then run your migration tool against 127.0.0.1 in the next step of the same job. TLS stays end to end. Drivers with SOCKS5 support can use FIXEDMARK_SOCKS_URL instead. 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" - name: Deploy over SSH through the static IP
env:
SOCKS_USER: ${{ secrets.FIXEDMARK_APP_ID }}
SOCKS_PASS: ${{ secrets.FIXEDMARK_TOKEN }}
run: |
sudo apt-get install -y ncat
ssh -o ProxyCommand="ncat --proxy mum.egress.fixedmark.com:1080 \
--proxy-type socks5 --proxy-auth $SOCKS_USER:$SOCKS_PASS %h %p" \
deploy@server.example.com './deploy.sh'Common errors and fixes
The secret is empty in pull requests from forks
GitHub does not pass secrets to workflows triggered from forks. That is the safe default. Run the proxied step only on push or after review.
ssh: connect to host ... Connection timed out during deploy
SSH does not read HTTPS_PROXY. Use the SOCKS5 ProxyCommand with ncat shown above, or the tunnel.
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 GitHub Actions
SSH deploys (ssh, rsync, scp) do not read HTTPS_PROXY. Use the tunnel, or an SSH ProxyCommand with ncat, which supports SOCKS5 with a username and password. OpenBSD nc does not send SOCKS5 credentials.
- Workflows from forks do not receive secrets, so pull requests from forks cannot use the proxy.
- Setting
HTTPS_PROXYfor the whole job also proxiesactions/cacheand artifact uploads. Keep it on the steps that need it.
Related guides
- Use casePostgres and MySQLAllowlist two IPs on RDS, Cloud SQL, or a self-hosted database and connect through SOCKS5 or a local tunnel.
- Use casePartner API allowlistingGive Salesforce, SOAP vendors, and payment gateways one dedicated IP pair to allowlist, wherever your code runs.
- Integrationn8nFixed IPs for n8n HTTP Request nodes on n8n Cloud or self-hosted.
- IntegrationAWS LambdaA fixed IP for Lambda without VPC, subnets, and two NAT Gateways.
Frequently asked questions
Do GitHub Actions runners have static IPs?
Standard GitHub-hosted runners do not. They use large, shared ranges that change. Larger runners with static IP ranges need GitHub Enterprise Cloud.
Should I set HTTPS_PROXY for the whole job?
No. Set it on the steps that need a fixed IP. Job-wide, checkout, caches, and artifact uploads would also go through the proxy and use your bandwidth.
Why does my Node script ignore HTTPS_PROXY?
Node's built-in fetch reads proxy environment variables only when NODE_USE_ENV_PROXY=1 is set, on Node 22.21+ or 24+. On older Node, pass the proxy URL to undici's ProxyAgent as the dispatcher option.
Can I run database migrations through the static IP?
Yes. Start the bm tunnel CLI in one step and run migrations against 127.0.0.1 in the next, or use a driver that supports SOCKS5.
Fix your egress IP.
Fixedmark is in early access. Join the list to get a dedicated IP pair for GitHub Actions when your region opens.