Static IP for Postgres and MySQL Allowlists
To connect a serverless or PaaS app to an IP-restricted Postgres or MySQL database, send the database connection through a static IP and allow only that IP in the database firewall. Fixedmark supports this with SOCKS5 for drivers that accept a custom dialer and the `bm tunnel` CLI for every other driver.
Where the database allowlist lives
Every managed database has a network allowlist, and each one wants a fixed source address:
- AWS RDS and Aurora: an inbound rule on the instance's security group, for example TCP 5432 from
203.0.113.10/32. - Google Cloud SQL: authorized networks on an instance with a public IP.
- Self-hosted Postgres: a
hostsslline inpg_hba.confplus your cloud or host firewall. - Self-hosted MySQL: the host part of the user account, such as
'app'@'203.0.113.10', plus the firewall.
Why this breaks on serverless and PaaS
Functions on Vercel, Netlify, and AWS Lambda outside a VPC connect from changing addresses. Containers on Railway, Render, and Heroku get new IPs when they move. Teams end up opening port 5432 or 3306 to 0.0.0.0/0, which exposes the database to internet-wide scanning and password guessing.
On AWS, you can place Lambda in a VPC and route through a NAT gateway with an Elastic IP. That works for compute inside AWS, but a highly available setup across two Availability Zones costs about $66 per month in gateway hours, about $73 with two public IPv4 addresses, before traffic. The NAT gateway comparison covers the numbers.
Choose a connection path
SOCKS5 is the best fit for serverless functions, because nothing extra runs beside your code. Go's pgx driver accepts a custom DialFunc, so you can dial through golang.org/x/net/proxy with a few lines. Check your driver's documentation for a SOCKS5 or custom socket option.
The bm tunnel CLI, planned for launch, forwards a local port to the database through your Fixedmark IPs. Run it in the start command of a long-lived service on Railway, Render, Fly.io, or Heroku, then point any driver at 127.0.0.1. Serverless functions cannot run a background tunnel, so use SOCKS5 there.
TLS still runs from your driver to the database. When you connect to 127.0.0.1 through a tunnel, set the TLS server name to the real database hostname so certificate checks pass.
Allowlist a static IP on your database
Add the new rule before you remove the old one, so there is no downtime.
Fixedmark is in early access. Proxy URLs are issued at launch. Join early access to get yours when your region opens.
- 1Create an endpoint near the databaseChoose the Fixedmark region closest to the database, for example Virginia for RDS in us-east-1.
- 2Allow both IPsAdd each IP as a /32 to the security group, authorized networks, or
pg_hba.conf. Restrict the rule to the database port. - 3Store the SOCKS URLSet
FIXEDMARK_SOCKS_URLon your platform. For the tunnel path, the CLI reads its token from its own config. - 4Connect through SOCKS5 or the tunnelUse a custom dialer in drivers that support one. Otherwise start
bm tunneland point the connection string at localhost. - 5Close the open ruleOnce connections succeed, remove any
0.0.0.0/0rule and confirm in the connection logs that traffic uses your pair.
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)
}# 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"// Runs alongside: bm tunnel 5432:db.example.com:5432
import pg from "pg";
const pool = new pg.Pool({
host: "127.0.0.1",
port: 5432,
user: "app",
password: process.env.DB_PASSWORD,
database: "app",
// Verify the real server name, not 127.0.0.1.
ssl: { servername: "db.example.com" },
});# Forward local port 3306 to the database through your Fixedmark IPs.
bm tunnel 3306:mysql.example.com:3306 &
# Connect to the local end. TLS still runs end to end to the server.
mysql --host=127.0.0.1 --port=3306 --user=app -p --ssl-mode=REQUIRED appRelated guides
- Use caseMongoDB AtlasReplace 0.0.0.0/0 in your Atlas IP access list with two dedicated IPs, using the Node driver's SOCKS5 support.
- IntegrationAWS LambdaA fixed IP for Lambda without VPC, subnets, and two NAT Gateways.
- IntegrationRailwayA dedicated IP pair for Railway services when shared static IPs are not enough.
- ComparisonAWS NAT GatewayThe real monthly cost of a highly available AWS NAT Gateway, and when a static IP proxy is simpler.
- ToolNAT gateway cost calculatorEstimate what a NAT gateway with Elastic IPs would cost for your traffic.
Frequently asked questions
How do I connect Vercel to an IP-restricted Postgres database?
Route the driver through a SOCKS5 proxy with a static IP, then allow that IP in the database firewall. Drivers with a custom dialer option, such as Go's pgx, can do this directly.
Can I use bm tunnel inside a serverless function?
No. Functions cannot keep a background process running. Use the tunnel on long-lived services and SOCKS5 inside functions.
Does the tunnel break TLS certificate checks?
Only if the driver verifies against 127.0.0.1. Set the TLS server name to the real database hostname and verification works as usual.
Is this cheaper than a NAT gateway?
For most small and mid-sized apps, yes. Two NAT gateways for high availability cost about $73 per month including their public IPv4 addresses, before data processing charges, and they only serve compute inside your AWS VPC.
Make it fixed.
Fixedmark is in early access. Join the list to get dedicated static IPs when your region opens.