Skip to content
Fixedmark
Docs

Connect databases through a static IP

Database drivers do not speak HTTP CONNECT. Send Postgres, MySQL, MongoDB, and Redis connections through your static IPs with a SOCKS5 dialer, or with the planned bm tunnel CLI for drivers that have no proxy option.

Docs preview: Fixedmark is not live yet. Endpoints are issued at launch, and these pages describe planned launch behavior. Anything marked planned may change. Join early access to get yours first.

SOCKS5 or bm tunnel

  • SOCKS5 works inside your process, so it fits serverless functions. The driver needs a proxy option or a custom dial function. Use FIXEDMARK_SOCKS_URL with the socks5h:// scheme.
  • bm tunnel (planned) forwards a local port to the database through your static IPs. Any driver works, because it just connects to 127.0.0.1. It needs a long-lived process next to your app, so it fits Railway, Render, Fly.io, Heroku, VMs, and containers. See the bm tunnel reference.

Both carry raw TCP. Your database's own TLS runs end to end, and the proxy never sees queries or results. Allowlist both IPs of your pair in the database firewall first.

Driver support

Proxy support by database driver
DriverSOCKS5Use
Go pgxCustom DialFuncSOCKS5 dialer
Go go-sql-driver/mysqlRegisterDialContextSOCKS5 dialer
Go go-redisCustom DialerSOCKS5 dialer plus TLS
MongoDB Node.js driverproxyHost optionsSOCKS5 options
libpq, psql, psycopgNonebm tunnel
node-postgres (pg)None built inbm tunnel
mysql2, PyMySQL, PDONone built inbm tunnel
ioredis, redis-pyNone built inbm tunnel

Postgres

In Go, set pgx's DialFunc to the SOCKS5 dialer from the Go guide. pgx still verifies TLS against the host name in DATABASE_URL.

libpq-based clients and node-postgres have no SOCKS5 option. Run bm tunnel and connect to 127.0.0.1. With sslmode=verify-full, keep the real name in host and put the tunnel address in hostaddr, so the certificate check uses the real name.

// Uses socksDialer() from the Go guide.
func connectPostgres(ctx context.Context) (*pgxpool.Pool, error) {
	dialer, err := socksDialer()
	if err != nil {
		return nil, err
	}
	cfg, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
	if err != nil {
		return nil, err
	}
	// pgx still runs TLS against the host name in DATABASE_URL.
	cfg.ConnConfig.DialFunc = dialer.DialContext
	return pgxpool.NewWithConfig(ctx, cfg)
}

Managed Postgres such as AWS RDS and Cloud SQL take the pair as two /32 rules. Self-hosted servers need both IPs in pg_hba.conf and the firewall. See Postgres and MySQL allowlisting.

MySQL

go-sql-driver/mysql lets you register a named dial function. The network name in the DSN selects it. Other MySQL drivers need bm tunnel. Through a tunnel, VERIFY_IDENTITY compares the certificate with 127.0.0.1 and fails. Use VERIFY_CA, or a driver option that sets the expected server name.

// Uses socksDialer() from the Go guide.
func openMySQL() (*sql.DB, error) {
	dialer, err := socksDialer()
	if err != nil {
		return nil, err
	}
	mysql.RegisterDialContext("fixedmark",
		func(ctx context.Context, addr string) (net.Conn, error) {
			return dialer.DialContext(ctx, "tcp", addr)
		})
	// The network name in the DSN selects the dialer:
	// app:secret@fixedmark(mysql.example.com:3306)/app?tls=true
	return sql.Open("mysql", os.Getenv("MYSQL_DSN"))
}

MongoDB

The MongoDB Node.js driver supports SOCKS5 through the proxyHost, proxyPort, proxyUsername, and proxyPassword options. Install the socks package next to it. The driver sends host names to the proxy, so each replica set member resolves on the proxy side.

// npm install mongodb socks
import { MongoClient } from "mongodb";

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();

With a mongodb+srv:// URI, the driver still looks up the SRV and TXT records locally. Only the TCP connections go through the proxy, which is what Atlas's IP access list checks. A tunnel is a poor fit for replica sets, because each member needs its own forwarded port. See MongoDB Atlas allowlisting.

Redis

go-redis takes a custom Dialer. When you set one, go-redis no longer adds TLS itself, so wrap the connection in TLS in the dialer for rediss:// URLs. Other clients need bm tunnel. Pass the real host name for TLS SNI.

// Uses socksDialer() from the Go guide. go-redis skips its own TLS
// when you set Dialer, so wrap the connection in TLS here.
func newRedis() (*redis.Client, error) {
	dialer, err := socksDialer()
	if err != nil {
		return nil, err
	}
	opt, err := redis.ParseURL(os.Getenv("REDIS_URL")) // rediss://...
	if err != nil {
		return nil, err
	}
	tlsCfg := opt.TLSConfig
	opt.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
		conn, err := dialer.DialContext(ctx, network, addr)
		if err != nil || tlsCfg == nil {
			return conn, err
		}
		return tls.Client(conn, tlsCfg), nil
	}
	return redis.NewClient(opt), nil
}

SSH, SFTP, and other TCP

OpenSSH can dial through SOCKS5 with a ProxyCommand, but it has no built-in SOCKS5 password support. Forward a port with the planned bm tunnel instead, and keep host key checks tied to the real host name.

# Planned: bm tunnel 2222:server.example.com:22
ssh -p 2222 -o HostKeyAlias=server.example.com deploy@127.0.0.1

Pools and timeouts

  • Each new database connection is one proxy request. Pools keep connections open, so usage stays low.
  • The proxy closes idle connections after a timeout, planned at 5 minutes by default and configurable for database tunnels. Set the pool's idle timeout below it.
  • During failover, open connections on the failed node drop. Make sure your pool checks connections before use or retries on reset.
  • Serverless functions open new connections often. Keep a pool at module scope so warm invocations reuse it.

Platform guides

Frequently asked questions

How do I connect to an IP-restricted database from serverless?

Allowlist both Fixedmark IPs in the database firewall. Then dial through SOCKS5 if your driver supports a custom dialer or proxy option, or run the planned bm tunnel on a platform with long-lived processes.

Does the database TLS still work through the proxy?

Yes. SOCKS5 and bm tunnel carry raw TCP, so the driver negotiates TLS with the database end to end. Through a local tunnel, tell the driver the real server name so certificate checks pass.

Why do idle database connections drop?

The proxy closes idle connections after a timeout, planned at 5 minutes by default. Set your pool's idle timeout lower, or enable TCP keepalive, so the pool replaces connections before the proxy closes them.

Can I use bm tunnel in Vercel or Lambda functions?

No. Functions cannot run a background process. Use a driver with SOCKS5 support there, such as the MongoDB Node.js driver or Go drivers with a custom dialer.

Make it fixed.

Fixedmark is in early access. Join the list to get dedicated static IPs when your region opens.