How to Route PyMongo Through a SOCKS Proxy on PythonAnywhere, Netlify, and Other Platforms That Restrict Custom Binaries

February 20, 2026
5 min read
Pattern

If your platform doesn't allow you to run custom binaries, you can't use QGTunnel, QuotaGuard's standard wrapper process. The fix is a pure-Python SOCKS approach using PySocks and pymongo that works on any managed or restricted runtime. This post covers the setup, the one MongoDB-specific gotcha that will block you if you miss it, and which platforms this applies to.

Which Platforms This Affects

QGTunnel works by running as a wrapper process that manages network routing for your application. Platforms that give you full control over your runtime (Heroku, Render, Docker, Kubernetes, GCP) support this approach with no issues. Platforms that restrict what you can run in your environment do not.

The pure-Python approach described here is what you need on:

  • PythonAnywhere — managed Python hosting, no custom binaries
  • Netlify Functions — serverless, no persistent process
  • Supabase Edge Functions — Deno-based, no binary access
  • n8n (cloud-hosted) — no-code automation, no runtime access
  • Make (Integromat) — same constraint
  • Zapier — same constraint
  • Bubble.io — managed no-code platform
  • Pantheon — managed WordPress/Drupal hosting

If you're on one of these and trying to connect to a firewalled MongoDB instance, the rest of this post is for you.

The mongodb+srv:// Gotcha

Before touching any proxy configuration, you need to deal with this. MongoDB Atlas gives you a mongodb+srv:// connection string by default. The problem is that the SRV DNS lookup happens before your proxy is involved, so it bypasses the SOCKS tunnel entirely. You need to convert it to a standard mongodb:// URI with explicit hosts first.

Run this once from your local machine or anywhere you can reach MongoDB directly:

from pymongo import MongoClient

client = MongoClient(
    "mongodb+srv://user:password@your-cluster.mongodb.net/",
    serverSelectionTimeoutMS=10000
)
info = client.admin.command("isMaster")
print("Replica Set:", info.get("setName"))
print("Hosts:", info.get("hosts"))

That will output your replica set name and the explicit hostnames. Use them to build a standard URI:

mongodb://user:password@host1:27017,host2:27017,host3:27017/?ssl=true&replicaSet=<setName>&authSource=admin

Set that as your MONGO_URI environment variable. This is what gets routed through the proxy.

Setting Up the Proxy

Install the two dependencies:

pip install pymongo PySocks

Set two environment variables in your platform's dashboard:

  • QUOTAGUARDSTATIC_URL — your proxy URL from the QuotaGuard dashboard, e.g. socks5://user:password@eu-west-static-01.quotaguard.com:1080
  • MONGO_URI — the explicit mongodb:// URI you built above (not the mongodb+srv:// version)

Three Things That Will Break It If You Miss Them

Before showing the full script, these three details are what separates working code from a frustrating afternoon of SSL errors and timeouts.

1. Import order matters. configure_socks_proxy() must be called before import pymongo. PySocks patches the socket layer globally at call time. If pymongo loads first, it captures the original socket and the proxy never gets involved.

2. Use rdns=True. This tells PySocks to send hostnames to the proxy server for resolution rather than resolving them locally first. Without it, DNS lookups happen on your machine before the connection is tunneled, which defeats the purpose on restricted platforms and can cause silent failures where traffic appears to route correctly but your actual IP is still exposed.

3. Delete SOCK_CLOEXEC after patching. This is the non-obvious one. PyMongo creates sockets with SOCK_CLOEXEC OR'd into the type flag, producing a socket type value that Python's ssl.SSLSocket then rejects with "Socket type must be stream or datagram." The error doesn't mention SOCK_CLOEXEC at all, so it looks like an unrelated SSL problem. Deleting SOCK_CLOEXEC from the socket module causes PyMongo to fall back to plain SOCK_STREAM sockets that SSL accepts. Without this fix you will get a cryptic SSL error with no clear path to the cause.

The Working Script

import os
import socket as _socket
import socks
from urllib.parse import urlparse


def configure_socks_proxy():
    proxy_url = os.getenv("QUOTAGUARDSTATIC_URL") or os.getenv("QUOTAGUARD_URL")
    if not proxy_url:
        raise RuntimeError(
            "Set QUOTAGUARDSTATIC_URL to your QuotaGuard proxy URL."
        )

    proxy = urlparse(proxy_url)
    if proxy.scheme not in ("socks5", "socks5h"):
        raise RuntimeError(f"Expected socks5:// URL, got: {proxy.scheme!r}")

    socks.set_default_proxy(
        socks.SOCKS5,
        proxy.hostname,
        int(proxy.port),
        rdns=True,           # Resolve hostnames on the proxy side, not locally
        username=proxy.username,
        password=proxy.password,
    )

    # Replace socket.socket so all connections use the proxy
    _socket.socket = socks.socksocket

    # PyMongo ORs SOCK_CLOEXEC into the socket type flag, producing a value
    # that ssl.SSLSocket rejects as "Socket type must be stream or datagram".
    # Removing it here causes PyMongo to fall back to plain SOCK_STREAM.
    if hasattr(_socket, "SOCK_CLOEXEC"):
        del _socket.SOCK_CLOEXEC


# Must be called before importing pymongo
configure_socks_proxy()

from pymongo import MongoClient  # noqa: E402


def main():
    mongo_uri = os.environ.get("MONGO_URI")
    if not mongo_uri:
        raise RuntimeError("Set MONGO_URI to a standard mongodb:// connection string.")

    if mongo_uri.startswith("mongodb+srv://"):
        raise RuntimeError(
            "mongodb+srv:// URIs are not supported — convert to mongodb:// first. "
            "See the section above for instructions."
        )

    print("Connecting to MongoDB via QuotaGuard SOCKS5 proxy...")
    client = MongoClient(mongo_uri, serverSelectionTimeoutMS=10000)

    result = client.admin.command("ping")
    print(f"MongoDB ping: {result}")
    print("✓ Connected to MongoDB through QuotaGuard Static")

    # Your database logic here
    db = client["your_database"]
    collection = db["your_collection"]

    for doc in collection.find().limit(5):
        print(doc)


if __name__ == "__main__":
    main()

One More Thing to Watch

socks.set_default_proxy() patches the socket layer for the entire Python process. Every outbound connection your script makes, not just MongoDB, will route through the proxy after that call. For a dedicated script that only talks to MongoDB that's fine. If you're integrating this into a larger application that also makes HTTP requests or connects to other services, be deliberate about where you place that call and what else is running in the same process.

Verifying It's Working

After a successful connection, you can confirm traffic is routing through your static IP by checking against QuotaGuard's IP reflection endpoint. Add this before your MongoDB logic:

import requests

# Call this after configure_socks_proxy(), before your MongoDB connection
proxy_url = os.environ.get("QUOTAGUARDSTATIC_URL")
proxies = {"http": proxy_url, "https": proxy_url}
response = requests.get("https://ip.quotaguard.com", proxies=proxies)
print(f"Outbound IP: {response.json()['ip']}")

The IP returned should match one of your two static IPs shown in the QuotaGuard dashboard. If it does, your MongoDB traffic is routing through the same path.


QuotaGuard provides static IP SOCKS and HTTP proxies for cloud applications, AI agents, and automation workflows. If your platform restricts custom binaries and you need to reach a firewalled database or API, start here.

QuotaGuard Static IP Blog

Practical notes on routing cloud and AI traffic through Static IPs.

Reliability Engineered for the Modern Cloud

For over a decade, QuotaGuard has provided reliable, high-performance static IP and proxy solutions for cloud environments like Heroku, Kubernetes, and AWS.

Get the fixed identity and security your application needs today.