QuotaGuard and Fly.io Integration Guide

    QuotaGuard and Fly.io Integration Guide

    QuotaGuard Static IPs allow your Fly.io applications to send outbound traffic through a load-balanced pair of static IP addresses. Once set up, you can use QuotaGuard’s IPs to connect to firewalled databases and APIs that require IP allowlisting.

    You do not need QuotaGuard for internal Fly.io private networking. Fly.io’s 6PN (IPv6 private network) handles communication between your apps within the same organization. QuotaGuard is for connecting to external services that require a known, static source IP address.

    Why Fly.io Apps Need Static IPs

    Fly.io uses an Anycast network designed for global distribution. Machines are ephemeral and scale automatically. This architecture means outbound traffic originates from shared IP pools that change without notice.

    Fly.io’s own documentation states they discourage using outbound IPs to bypass firewalls because a machine’s outbound IP is liable to change.

    Common scenarios where you need a static IP:

    • MongoDB Atlas: Requires IP allowlisting in Network Access settings
    • Supabase/Neon: Database connection restrictions
    • Amazon RDS: Security groups with IP-based rules
    • Payment Gateways: Banking APIs that only accept requests from registered IPs
    • Partner APIs: Corporate firewalls that require known source addresses
    • Legacy Systems: On-premise databases and ERPs with strict firewall policies

    Native Fly.io Static Egress vs QuotaGuard

    Fly.io launched app-scoped static egress IPs in late 2024. Here’s how the two options compare:

    FeatureFly.io Native EgressQuotaGuard
    Pricing$3.60/month per IPv4 per regionStarting at $19/month
    Multi-regionRequires separate IP per regionOne proxy serves all regions
    Machine limit64 machines per IPNo machine limit
    Protocol supportHTTP/HTTPS (TCP requires extra work)HTTP/HTTPS + SOCKS5 for databases
    Database connectionsRequires additional configurationBuilt-in SOCKS5 support
    HIPAA/PCI complianceNo compliance featuresShield product for regulated data
    ReliabilitySome intermittent issues reportedLoad-balanced with failover

    Use Fly.io Native Egress if you have a single-region deployment, only need HTTP/HTTPS, have fewer than 64 machines, and budget is the primary concern.

    Use QuotaGuard if you need multi-region support with a single IP identity, database connections (MongoDB, PostgreSQL), HIPAA/PCI compliance, or production reliability with dedicated support.

    Getting Started

    After creating a QuotaGuard account, you will be redirected to your dashboard where you can find your proxy credentials and static IP addresses.

    Choose the right proxy region: Match your QuotaGuard region to your primary Fly.io deployment region to minimize latency.

    Fly.io RegionLocationRecommended QuotaGuard Region
    iadAshburn, VirginiaUS-East
    laxLos Angeles, CaliforniaUS-West
    seaSeattle, WashingtonUS-West
    yyzToronto, CanadaCanada (Montreal)
    lhrLondon, UKEU-West (Ireland)
    fraFrankfurt, GermanyEU-Central (Frankfurt)
    amsAmsterdam, NetherlandsEU-West (Ireland)
    nrtTokyo, JapanAP-Northeast (Tokyo)
    sinSingaporeAP-Southeast (Singapore)
    sydSydney, AustraliaAustralia (Sydney)

    To check your Fly.io app’s region, run:

    fly status -a your-app-name
    

    Look for the REGION column in the Machines list.

    Configuring Your Application

    Step 1: Add Your Proxy URL as a Secret

    Store your QuotaGuard credentials securely using Fly.io secrets:

    fly secrets set QUOTAGUARDSTATIC_URL="http://username:password@us-east-static-01.quotaguard.com:9293" -a your-app-name
    

    Fly.io secrets are encrypted and only exposed to your application instances at runtime. This keeps your credentials out of your Git repository.

    To verify the secret was set:

    fly secrets list -a your-app-name
    

    You should see QUOTAGUARDSTATIC_URL in the list (the value is hidden).

    Step 2: Configure Your HTTP Client

    Python (requests)

    import os
    import requests
    
    proxy_url = os.environ.get('QUOTAGUARDSTATIC_URL')
    
    proxies = {
        'http': proxy_url,
        'https': proxy_url
    }
    
    response = requests.get('https://api.example.com/data', proxies=proxies)
    print(response.json())
    

    Python (httpx)

    import os
    import httpx
    
    proxy_url = os.environ.get('QUOTAGUARDSTATIC_URL')
    
    with httpx.Client(proxy=proxy_url) as client:
        response = client.get('https://api.example.com/data')
        print(response.json())
    

    Node.js (axios with https-proxy-agent)

    const axios = require('axios');
    const { HttpsProxyAgent } = require('https-proxy-agent');
    
    const proxyUrl = process.env.QUOTAGUARDSTATIC_URL;
    const agent = new HttpsProxyAgent(proxyUrl);
    
    axios.get('https://api.example.com/data', { httpsAgent: agent })
      .then(response => console.log(response.data))
      .catch(error => console.error(error));
    

    Install the proxy agent: npm install https-proxy-agent

    Node.js (fetch with undici)

    import { ProxyAgent, fetch } from 'undici';
    
    const proxyUrl = process.env.QUOTAGUARDSTATIC_URL;
    const dispatcher = new ProxyAgent(proxyUrl);
    
    const response = await fetch('https://api.example.com/data', { dispatcher });
    const data = await response.json();
    console.log(data);
    

    Go

    package main
    
    import (
        "fmt"
        "io"
        "net/http"
        "net/url"
        "os"
    )
    
    func main() {
        proxyURL, err := url.Parse(os.Getenv("QUOTAGUARDSTATIC_URL"))
        if err != nil {
            panic(err)
        }
    
        client := &http.Client{
            Transport: &http.Transport{
                Proxy: http.ProxyURL(proxyURL),
            },
        }
    
        resp, err := client.Get("https://api.example.com/data")
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()
    
        body, _ := io.ReadAll(resp.Body)
        fmt.Println(string(body))
    }
    

    Ruby (Net::HTTP)

    require 'net/http'
    require 'uri'
    
    proxy_uri = URI.parse(ENV['QUOTAGUARDSTATIC_URL'])
    
    http = Net::HTTP.new(
      'api.example.com',
      443,
      proxy_uri.host,
      proxy_uri.port,
      proxy_uri.user,
      proxy_uri.password
    )
    http.use_ssl = true
    
    response = http.get('/data')
    puts response.body
    

    Ruby (Faraday)

    require 'faraday'
    
    conn = Faraday.new(url: 'https://api.example.com') do |f|
      f.proxy = ENV['QUOTAGUARDSTATIC_URL']
      f.adapter Faraday.default_adapter
    end
    
    response = conn.get('/data')
    puts response.body
    

    Elixir (HTTPoison)

    proxy_url = System.get_env("QUOTAGUARDSTATIC_URL")
    uri = URI.parse(proxy_url)
    [username, password] = String.split(uri.userinfo, ":")
    
    options = [
      proxy: {String.to_charlist(uri.host), uri.port},
      proxy_auth: {String.to_charlist(username), String.to_charlist(password)}
    ]
    
    {:ok, response} = HTTPoison.get("https://api.example.com/data", [], options)
    IO.inspect(response.body)
    

    Elixir (Req)

    proxy_url = System.get_env("QUOTAGUARDSTATIC_URL")
    
    Req.get!("https://api.example.com/data",
      connect_options: [proxy: proxy_url]
    )
    

    Elixir (Finch)

    # In your application supervision tree
    proxy_url = System.get_env("QUOTAGUARDSTATIC_URL")
    uri = URI.parse(proxy_url)
    [username, password] = String.split(uri.userinfo, ":")
    
    Finch.start_link(
      name: MyFinch,
      pools: %{
        :default => [
          conn_opts: [
            proxy: {:http, uri.host, uri.port, []},
            proxy_headers: [
              {"proxy-authorization", "Basic " <> Base.encode64("#{username}:#{password}")}
            ]
          ]
        ]
      }
    )
    
    # Making a request
    Finch.build(:get, "https://api.example.com/data")
    |> Finch.request(MyFinch)
    

    Rust (reqwest)

    use reqwest::Proxy;
    use std::env;
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let proxy_url = env::var("QUOTAGUARDSTATIC_URL")?;
        let proxy = Proxy::all(&proxy_url)?;
        
        let client = reqwest::Client::builder()
            .proxy(proxy)
            .build()?;
        
        let response = client.get("https://api.example.com/data")
            .send()
            .await?;
        
        println!("{}", response.text().await?);
        Ok(())
    }
    

    Add to Cargo.toml:

    [dependencies]
    reqwest = { version = "0.11", features = ["json"] }
    tokio = { version = "1", features = ["full"] }
    

    Database Connections (SOCKS5)

    For non-HTTP protocols like PostgreSQL, MySQL, or MongoDB, use QuotaGuard’s SOCKS5 proxy on port 1080.

    When to Use SOCKS5

    Standard HTTP proxies only handle HTTP/HTTPS traffic. If your Fly.io application connects to:

    • PostgreSQL
    • MySQL
    • MongoDB
    • Redis
    • SFTP
    • Any raw TCP connection

    You need SOCKS5 proxying.

    Step 1: Add SOCKS5 Credentials as a Secret

    Your SOCKS5 proxy URL is available in your QuotaGuard dashboard. Add it as a separate secret:

    fly secrets set QUOTAGUARD_SOCKS_HOST="us-east-static-01.quotaguard.com" -a your-app-name
    fly secrets set QUOTAGUARD_SOCKS_PORT="1080" -a your-app-name
    fly secrets set QUOTAGUARD_SOCKS_USER="your-username" -a your-app-name
    fly secrets set QUOTAGUARD_SOCKS_PASS="your-password" -a your-app-name
    

    Step 2: Configure Your Database Client

    Python (PostgreSQL with psycopg2 and PySocks)

    import os
    import socks
    import socket
    import psycopg2
    
    # Configure SOCKS proxy
    socks.set_default_proxy(
        socks.SOCKS5,
        os.environ.get('QUOTAGUARD_SOCKS_HOST'),
        int(os.environ.get('QUOTAGUARD_SOCKS_PORT')),
        username=os.environ.get('QUOTAGUARD_SOCKS_USER'),
        password=os.environ.get('QUOTAGUARD_SOCKS_PASS')
    )
    socket.socket = socks.socksocket
    
    # Connect to database
    conn = psycopg2.connect(
        host='your-database.example.com',
        database='mydb',
        user='dbuser',
        password='dbpass'
    )
    

    Install dependencies: pip install PySocks psycopg2-binary

    Node.js (MongoDB with socks)

    const { MongoClient } = require('mongodb');
    const SocksConnection = require('socksjs');
    
    const proxyHost = process.env.QUOTAGUARD_SOCKS_HOST;
    const proxyPort = parseInt(process.env.QUOTAGUARD_SOCKS_PORT);
    const proxyUser = process.env.QUOTAGUARD_SOCKS_USER;
    const proxyPass = process.env.QUOTAGUARD_SOCKS_PASS;
    
    const client = new MongoClient('mongodb+srv://your-cluster.mongodb.net/', {
      proxyHost: proxyHost,
      proxyPort: proxyPort,
      proxyUsername: proxyUser,
      proxyPassword: proxyPass
    });
    
    await client.connect();
    

    Using QGTunnel with Fly.io (Docker)

    For applications deployed as Docker containers, you can include QGTunnel in your image to transparently proxy database connections.

    Step 1: Download QGTunnel and add it to your project:

    curl https://s3.amazonaws.com/quotaguard/qgtunnel-latest.tar.gz | tar xz
    

    This creates bin/qgtunnel and vendor/nss_wrapper/.

    Step 2: Modify your Dockerfile to include QGTunnel:

    FROM node:18-slim
    
    WORKDIR /app
    
    # Copy application files
    COPY package*.json ./
    RUN npm install
    COPY . .
    
    # Copy QGTunnel
    COPY bin/qgtunnel /app/bin/qgtunnel
    COPY vendor /app/vendor
    
    # Make qgtunnel executable
    RUN chmod +x /app/bin/qgtunnel
    
    # Use qgtunnel as entrypoint
    ENTRYPOINT ["/app/bin/qgtunnel"]
    CMD ["node", "server.js"]
    

    Step 3: Configure your tunnel in the QuotaGuard dashboard:

    Navigate to Settings > Setup > Tunnel > Create Tunnel.

    Example PostgreSQL configuration:

    SettingValue
    Remote Destinationtcp://your-database.example.com:5432
    Local Port5432
    Transparenttrue
    Encryptedfalse

    Transparent mode overrides DNS so your app can connect to the original hostname while traffic routes through the tunnel.

    Step 4: Deploy with the QUOTAGUARDSTATIC_URL secret set. QGTunnel reads this automatically.

    fly.toml Configuration

    You can also set non-sensitive environment variables in your fly.toml file:

    [env]
      PROXY_REGION = "us-east"
      # Do NOT put QUOTAGUARDSTATIC_URL here - use secrets for credentials
    

    For the proxy URL, always use fly secrets set to keep credentials out of your repository.

    Testing Your Implementation

    Verify your static IP configuration by requesting ip.quotaguard.com:

    From Your Local Machine

    curl -x $QUOTAGUARDSTATIC_URL https://ip.quotaguard.com
    

    From a Running Fly.io Machine

    Use the Fly.io SSH console to test from inside your application:

    fly ssh console -a your-app-name
    

    Then from inside the machine:

    curl -x $QUOTAGUARDSTATIC_URL https://ip.quotaguard.com
    

    Expected response:

    {"ip":"52.34.188.175"}
    

    The returned IP should match one of your two static IPs shown in the QuotaGuard dashboard. Run it multiple times to see both IPs (load-balanced).

    Application-Level Test

    Add a test endpoint to your application:

    # Python/Flask example
    import os
    import requests
    from flask import Flask, jsonify
    
    app = Flask(__name__)
    
    @app.route('/test-proxy')
    def test_proxy():
        proxy_url = os.environ.get('QUOTAGUARDSTATIC_URL')
        proxies = {'http': proxy_url, 'https': proxy_url}
        
        response = requests.get('https://ip.quotaguard.com', proxies=proxies)
        return jsonify({
            'static_ip': response.json()['ip'],
            'proxy_configured': True
        })
    

    Latency Considerations

    Using QuotaGuard adds a network hop to your requests:

    ConfigurationAdded Latency
    Same region (Fly.io iad + QuotaGuard US-East)10-20ms
    Cross-region50-100ms

    Fly.io apps can run in multiple regions simultaneously. All instances will use the same QuotaGuard static IPs regardless of which edge region serves the request. This is an advantage over native egress, which requires separate IPs per region.

    For latency-sensitive applications, select a QuotaGuard region that matches your primary Fly.io deployment region.

    Multi-Region Deployments

    If your Fly.io app runs in multiple regions (e.g., iad, fra, sin), QuotaGuard provides a significant advantage over native egress.

    With Native Egress: You need a separate static IP for each region ($3.60/month × 3 regions = $10.80/month), and partners must allowlist all of them.

    With QuotaGuard: All regions route through the same proxy. Partners allowlist just two IPs (your load-balanced pair). Machines in any region connect to the same QuotaGuard endpoint.

    To minimize latency in multi-region setups, you can configure region-specific QuotaGuard endpoints using Fly.io’s PRIMARY_REGION environment variable:

    import os
    
    region = os.environ.get('FLY_REGION', 'iad')
    
    # Map Fly.io regions to QuotaGuard endpoints
    proxy_map = {
        'iad': os.environ.get('QUOTAGUARD_US_EAST'),
        'fra': os.environ.get('QUOTAGUARD_EU'),
        'sin': os.environ.get('QUOTAGUARD_APAC'),
    }
    
    proxy_url = proxy_map.get(region, proxy_map['iad'])
    

    Troubleshooting

    407 Proxy Authentication Required

    Your credentials are incorrect. Verify your QUOTAGUARDSTATIC_URL secret:

    fly ssh console -a your-app-name
    echo $QUOTAGUARDSTATIC_URL
    

    Check that the username and password match your QuotaGuard dashboard.

    Connection Timeout

    1. Verify your Fly.io app can reach external networks
    2. Check that the QuotaGuard proxy hostname is correct
    3. Ensure no firewall rules block outbound connections to port 9293

    Wrong IP Address Returned

    The proxy URL may not be configured correctly:

    1. Verify the environment variable is being read in your code
    2. Check that you’re applying the proxy to the specific request, not just setting an environment variable
    3. Some HTTP clients require explicit proxy configuration per-request

    Secrets Not Loading

    Fly.io secrets are injected at runtime, not build time. If your application reads environment variables during the Docker build, they won’t be available.

    Move configuration loading to runtime:

    # Wrong - runs at import time
    PROXY_URL = os.environ['QUOTAGUARDSTATIC_URL']  # May fail during build
    
    # Right - runs at request time
    def get_proxy():
        return os.environ.get('QUOTAGUARDSTATIC_URL')
    

    QGTunnel Not Starting

    1. Verify bin/qgtunnel is included in your Docker image and is executable
    2. Check that QUOTAGUARDSTATIC_URL is set as a secret
    3. Enable debug logging by setting QGTUNNEL_DEBUG=true:
    fly secrets set QGTUNNEL_DEBUG="true" -a your-app-name
    

    Then check logs:

    fly logs -a your-app-name | grep qgtunnel
    

    IPv6 vs IPv4 Issues

    Fly.io’s internal networking is primarily IPv6. Some applications may attempt IPv6 connections first. QuotaGuard provides dedicated static IPv4 addresses for allowlisting.

    If you’re seeing IPv6 addresses instead of your static IPv4:

    1. Ensure your HTTP client is using the proxy for the specific request
    2. Check that the destination service supports IPv4 (most do)
    3. Verify the proxy URL uses the correct hostname format

    QuotaGuard Static vs QuotaGuard Shield

    QuotaGuard offers two products for static IPs:

    FeatureQuotaGuard StaticQuotaGuard Shield
    ProtocolHTTP/SOCKS5HTTPS/SOCKS5 over TLS
    EncryptionStandard proxySSL Passthrough (E2EE)
    Best forGeneral API accessHIPAA, PCI-DSS, regulated data
    Starting price$19/month$69/month

    For most Fly.io applications, QuotaGuard Static provides everything you need. Choose Shield if you’re handling protected health information (PHI), payment card data, or have specific compliance requirements.


    Ready to Get Started?

    Get in touch or create a free trial account.

    Try QuotaGuard Now

    View Fly.io Integration Features

    Read: How Do I Get a Static IP for Fly.io Apps?


    Ready to Get Started?

    Get in touch or create a free trial account