IP whitelisting vs username password proxy auth

    IP whitelisting vs username password proxy auth: what each checks, where whitelists break in Docker, Kubernetes, CI and serverless, and what 407 means.

    · 16 min read · Authentication

    A proxy has to answer one question before it forwards a byte: is this client allowed. There are two ways to answer it. IP whitelisting vs username password proxy authentication is not a security debate — both models are fine — it is an operational one, and the operational answer depends entirely on whether your egress address is a fact about your infrastructure or an accident of it. Most comparison articles stop at "whitelisting is convenient, credentials are portable." That is true and useless. The interesting part is the list of environments where a whitelist quietly stops being a stable identifier, and what you give up when you drop the username.

    IP whitelisting vs username password proxy auth: the two models

    IP whitelisting Username and password
    What the gateway checks Source address of the TCP connection Credentials presented in the connection setup
    Where the secret lives Nowhere — the network path is the secret Config, env var, secret manager, or the proxy URL
    Per-request overhead None One Proxy-Authorization header, or the SOCKS5 auth sub-negotiation
    Breaks when Your egress IP changes Credentials rotate, leak, or are revoked
    Revocation Remove the address from the list Change the password
    Works from a laptop on hotel Wi-Fi No Yes
    Works from an autoscaling node pool Only with pinned egress Yes
    Carries targeting parameters No Yes — encoded in the username
    Blast radius if compromised Anyone sharing your egress IP Anyone holding the string

    The row that decides most real deployments is the last-but-one, and almost nobody writes about it. Hold that thought until the targeting section.

    The mechanics differ by protocol. Over HTTP, credentials ride in a Proxy-Authorization: Basic header on the request or on the CONNECT, base64 of user:pass, defined in RFC 7235. Over SOCKS5 they go in the username/password sub-negotiation from RFC 1929, a separate exchange before the connect request. Same credentials, different frame. On the AnonEdge gateway that is gw.anonedge.com port 823 for HTTP and HTTPS, port 824 for SOCKS5:

    curl -x "http://login:[email protected]:823" https://api.ipify.org/
    curl -x "socks5h://login:[email protected]:824" https://api.ipify.org/
    

    Under a whitelist, the same commands lose the login:password@ and nothing else changes. AnonEdge ships both. Its getting-started documentation lists two authentication methods — "Username/Password: Use the credentials provided in your dashboard" and "IP Allowlisting: Add your server's IP to allowlist and connect without credentials" — and the SimpleProxy walkthrough repeats the same rule: if your IP is allowlisted, credentials are not required. See your first authenticated request.

    One note on vocabulary before going further. AnonEdge's documentation calls this allowlisting. The search term, and the key name in most tooling, is whitelist. Same mechanism, two words. This page says "whitelisting" for the general model and "allowlist" where it refers to what AnonEdge actually ships.

    Where whitelisting wins

    No credentials in code, config, or logs. There is no string to leak, because there is no string. Every leakage vector in the next-but-one section simply does not exist. For a fixed-egress production box that is a real reduction in attack surface, not a theoretical one.

    Clients that cannot send proxy auth at all. This is the underrated case. Plenty of tooling speaks SOCKS5 but has no way to pass credentials, and plenty more speaks HTTP but drops Proxy-Authorization on redirects or on the CONNECT. Playwright documents optional username and password "for HTTP(S) proxy" specifically, and SOCKS5 proxy authentication has been an open feature request against the project since 2021 (playwright#10567). On that stack, a whitelist is not a preference — it is the only way to use SOCKS5 at all; Playwright and authenticated SOCKS5 covers what that costs you per browser context.

    Zero per-connection auth work. Nothing to parse, nothing to compare, no credential cache to invalidate. At high connection churn this is a small win that compounds.

    Nothing to rotate. No 90-day password policy, no secret-manager entry, no rollout window. The whitelist changes when your infrastructure changes, which is the correct coupling for infrastructure that does not change.

    Where whitelisting breaks

    This is the section the search results name and then never write. Every environment below shares one property: the address the gateway sees is produced by something other than the machine running your code.

    Dynamic residential and office IPs, and CGNAT

    A consumer or small-office connection gets its address from the ISP, on the ISP's schedule. A router reboot, a lease expiry, a line fault, a maintenance window — any of them can hand you a new address with no notification. Your job starts failing at 03:00 for a reason that has nothing to do with your code.

    Carrier-grade NAT makes it worse in the other direction. Under CGNAT your "public" address is shared with a large number of other subscribers, so whitelisting it authorizes all of them to use your proxy account. The address is simultaneously unstable and over-broad. Mobile networks are almost universally CGNAT, so a whitelist plus a phone tether is the worst combination available.

    Docker: the container's egress IP is not what you think

    On the default bridge network, a container does not have a public address. Its packets are source-NATed to the host's address by an iptables masquerade rule on the way out. The gateway sees the Docker host, not the container. That is usually what people expect.

    What people do not expect is that everything around it also changes. Switch to --network host and the container shares the host's network namespace directly — same visible address, different behavior for binds and for loopback. Put the container behind a Swarm overlay network and egress leaves via whichever node the task is scheduled onto. Run the same image on a developer laptop, in a build agent, and on a production host and you have three different egress addresses for one artifact.

    There is a second, sharper Docker trap that costs people hours: the Docker client's proxy settings and the Docker daemon's are different things. The Docker documentation is explicit that the proxies block in ~/.docker/config.json configures "proxy environment variables for containers only, and not used as proxy settings for the Docker CLI or the Docker Engine itself" (docs.docker.com). Pulling an image and running one are two separate network paths, and only one of them is your application. The full treatment of where proxy settings land in containers is a topic in its own right.

    Kubernetes: pod egress, NAT gateways, and node pools that scale your whitelist

    By default in most clusters a pod's outbound traffic is source-NATed to the node's address. So the gateway sees the node, and your whitelist has to contain every node your workload might land on. Then the cluster autoscaler adds a node at peak, that node gets a new address, and the pods scheduled onto it fail authentication while the identical pods on the old nodes keep working. The symptom is a partial outage that correlates with load, which is exactly the symptom people spend the longest misdiagnosing.

    The fix is to stop letting the node decide. Pin egress through a managed NAT with a fixed address — a cloud NAT gateway with a reserved address, or a dedicated egress gateway in the cluster — and whitelist that one address. Now node churn is invisible to the proxy. This works, it is the right answer for fixed-egress production, and it is a piece of infrastructure you now own and have to keep alive.

    CI runners

    Hosted CI runners are shared infrastructure. GitHub publishes the address ranges its Actions runners use under the actions key of the GET /meta endpoint, and the documentation is clear that you must query the API for current values rather than copying them. Those ranges are large, they are shared with every other customer on the platform, and they change.

    Whitelisting them has two problems at once. It is operationally fragile, because the list moves. And it is a weak control, because "any address in GitHub's runner range" authorizes a very large population that is not you. Self-hosted runners behind a fixed NAT solve both, at the cost of running runners.

    Serverless

    AWS states plainly that "by default, Lambda functions have access to the public internet," and that attaching a function to a VPC removes that access until you configure the VPC for it — "connecting a function to a public subnet doesn't give it internet access or a public IP address" (Lambda VPC documentation). An unattached function egresses from a provider-managed pool you neither choose nor can enumerate. A VPC-attached function egresses from whatever NAT you put in front of it.

    So on serverless, a whitelist is not something you configure on the function. It is something you buy: a NAT gateway with a reserved address, running permanently, for a workload whose whole appeal was not running permanently. That trade is sometimes correct. It should be made deliberately.

    Where user:pass wins, and what it costs

    Portability. The same string works from a laptop, a container, a runner and a Lambda. Nothing about the network path has to be true.

    Per-job credentials. Issue one set per pipeline, per customer, per environment. Now your proxy bill and your proxy logs are attributable, and a compromise is scoped to one job instead of one company.

    Instant revocation. Change the password and every holder of the old one stops working immediately, wherever they are. A whitelist can only revoke by address, which is useless when the address is shared.

    Targeting. Covered next, because it deserves its own section.

    The cost is secret sprawl. Every place that string lives is a place it can escape from, and proxy credentials are unusually prone to escaping because the most convenient way to express them is inside a URL.

    Credentials in a proxy URL leak

    http://login:[email protected]:823 is a password in a place designed for logging. Here is where it actually surfaces.

    Vector What exposes it
    Shell history ~/.bash_history, ~/.zsh_history — the whole curl line, in plaintext, forever
    Process list ps auxww shows every argument of every running process to every user on the box
    /proc/<pid>/environ Readable by the process owner and by root; survives for the life of the process
    docker inspect Prints the container's full Env array, credentials included, to anyone who can reach the socket
    Image layers A ENV HTTP_PROXY=http://user:pass@… in a Dockerfile is baked into a layer and travels with the image
    CI logs Failure paths that echo the effective config, plus any tool that prints its resolved proxy URL on error
    Crash reports and APM Exception payloads routinely include the request URL and the client configuration
    Redirect handling Some clients re-send Proxy-Authorization on cross-host redirects; check yours

    Three mitigations, in order of value. Keep the password out of the URL and pass it through the client's dedicated auth mechanism where one exists — curl -U user:pass -x http://gw.anonedge.com:823, for example, which keeps it out of the URL but still puts it in ps. Read it from a file or a secret manager at startup rather than from the environment. And prefix a space before interactive shell commands that carry it, if your shell honors HISTCONTROL=ignorespace.

    None of that helps if the credential is long-lived and widely distributed. Short lifetime and narrow scope beat careful handling.

    Targeting parameters ride in the username

    Here is the argument no comparison article makes, and it is the one that decides the model for most operators.

    Gateway proxies encode per-request behavior in the username. Country, state, city, ZIP, ASN, session identity, sometimes rotation policy — all of it travels as structured parameters appended to the login string, because the username is the only free-form field the proxy protocol gives a client to send per connection. That is the mechanism by which one hostname and one port can serve 195 countries.

    Remove the username and you remove the channel.

    A whitelist-only setup authenticates you, and that is all it can do. There is nowhere to put "give me a Brazilian mobile exit in ASN 28573 and hold it for this session." You are left with whatever the account default is, changed globally in a dashboard rather than per request. For a fleet of workers each collecting a different geography, that is not a workaround away from being usable — it is a different product.

    AnonEdge's default targeting — country selection and exclusion, ASN exclusion — is included in the base rate. The advanced state, city, ZIP and ASN filters are billed at 2x the standard rate, and a filter with no matching exits returns 400 NO_RAY rather than silently falling back. Both of those only reach the gateway if there is a username to carry them. Targeting parameters in the username works through what that 2x actually buys, and when it does not.

    If your workload is single-geography and static, this costs you nothing. If it is not, the targeting requirement settles the auth question before the security discussion starts.

    A hybrid that works

    The correct unit of decision is the job, not the company. Run both.

    Whitelist the fixed egress. Production workers behind a pinned NAT gateway, a self-hosted runner, a dedicated collection box. These have one address that changes at the speed of infrastructure change, which is to say rarely and deliberately. No credentials to manage, no rotation calendar.

    Use credentials for everything ephemeral or portable. Developer machines, hosted CI, serverless, autoscaling pools, anything that runs somewhere new each time. Issue per-job credentials so the bill and the logs stay attributable.

    Use credentials wherever you need targeting, regardless of how stable the egress is. This overrides the rule above. Pool type, rotation and country selection all live alongside the credential — see configuring credentials and pool settings.

    The rules that follow from that are short. If the address is produced by infrastructure you control and pay to keep alive, whitelist. If the address is produced by somebody else's scheduler, authenticate. If the request needs per-request geography, authenticate. If the client cannot send credentials, whitelist and accept the loss of targeting.

    Where both are available, requiring both is stricter than either — the connection has to come from a known address and present a valid credential. Reach for that on the jobs where a compromise would be expensive, not on everything, because it doubles the ways a deployment can fail.

    Rotating proxy credentials without downtime

    Credential rotation is the recurring cost of the user:pass model, and it is the step people skip until an incident forces it. Four phases, no downtime.

    1. Issue the new credential alongside the old. Both valid. Nothing has changed for any running worker.
    2. Roll the new credential out. Update the secret store, restart workers on your normal deployment cadence. Long-lived connections keep using the old credential until they cycle — that is fine, both work.
    3. Confirm the old credential is idle. Watch your traffic attribution until the old credential shows no activity across a full duty cycle, including whatever weekly or monthly job you forgot about. This is the step that determines whether rotation is safe, and it is the step that gets rushed.
    4. Revoke last. Only after the old credential has been quiet for a complete cycle.

    The overlap window has to be longer than your longest-lived session and longer than your least frequent scheduled job. If a monthly report job holds the only remaining reference, a two-week window guarantees an incident on the 30th.

    Sticky sessions extend the window in a way that is easy to underestimate. A session held on a port in the 10000–20000 range occupies that binding for as long as you hold it, and it holds a thread with it. Read thread limits and concurrency before you size the overlap.

    What 407 means under each model

    Same status code. Opposite fix. This is the single most common support ticket in proxy operations, and the auth model tells you which half of the problem space to search.

    Under user:pass, 407 is a credential problem. The gateway got a credential and rejected it. Check, in order: the password was actually substituted and you are not sending the literal string password; special characters in the credential are percent-encoded in the URL — @ becomes %40 and a raw @ truncates the host; the client is sending Proxy-Authorization and not Authorization, which are different headers for different hops; and the credential has not been rotated out from under you by step 4 above.

    Under whitelisting, 407 is an egress-IP problem. The gateway saw an address it does not recognize. Your code and your credentials are irrelevant. Find out what address the gateway actually saw before you change anything:

    # What does the outside world think you are?
    curl -s https://api.ipify.org/
    

    Run that from inside the exact context that is failing — inside the container, inside the CI step, inside the function — not from your terminal. The answer is frequently a node, a NAT gateway, or a provider range you did not know was in the path.

    There is a third case that is neither. AnonEdge documents 407 THREADS_EXHAUSTED, which means the concurrent connection limit on your plan is reached. Nothing is wrong with your credentials or your address; you are simply asking for more simultaneous connections than the plan allows. Thread limits are per-plan and increases go through support, which may require KYC. Sticky sessions make this easier to hit than it looks, because a held session occupies a thread for its whole life.

    Read the response body, not just the status line. The three cases are distinguishable, and treating them as one is how a capacity problem gets diagnosed as a password problem. Start at troubleshooting when the body is ambiguous.

    Frequently asked questions

    Is IP whitelisting more secure than a proxy username and password?

    Neither is inherently stronger. A whitelist removes the credential as a leakable object but authorizes everyone who shares your egress address, which under CGNAT or a hosted CI range can be thousands of unrelated parties. Credentials scope access to whoever holds the string, which is precise but leakable. The higher-security answer is both together, applied to the small set of jobs where a compromise would actually be expensive.

    Why does my whitelisted IP stop working inside Docker?

    Because the gateway never saw the container. On the default bridge network the container's packets are source-NATed to the Docker host, so the whitelisted address must be the host's — and on a build agent, an overlay network, or a different host, that address is different. Run curl -s https://api.ipify.org/ from inside the failing container to see what the gateway is actually authenticating.

    Can I use IP whitelisting with geo targeting?

    Not per request. Targeting parameters — country, state, city, ZIP, ASN, session identity — are encoded in the proxy username, so removing the username removes the only per-connection channel for them. A whitelist-only setup gets the account default, changed globally rather than per request. If different workers need different geographies simultaneously, you need credentials.

    What does 407 Proxy Authentication Required actually mean?

    It means the proxy refused the connection at the authentication step, and the cause depends on your model. Under credentials it is a bad, missing or rotated password, or a special character that was not percent-encoded. Under a whitelist it means the source address is not registered. On AnonEdge there is a third case, 407 THREADS_EXHAUSTED, which is a concurrency limit rather than an authentication failure at all.

    How often should I rotate proxy credentials?

    Rotate on a schedule you can actually complete without downtime, and immediately on any suspected exposure — a credential in a build log, a public repository, or a shared terminal recording. The mechanics matter more than the interval: issue the new credential first, roll it out, confirm the old one is idle across a full duty cycle including monthly jobs, and revoke last.

    Can I use both authentication models at the same time?

    Where a provider supports it, yes, and it is the strictest configuration available: the connection must originate from a registered address and present a valid credential. It also doubles the number of things that can break a deployment, so it belongs on high-value jobs rather than as a default. Ask support what your account supports before designing around it.

    Create credentials, then decide per job

    Start with a username and password, because it works everywhere and it is the only model that carries targeting. Move a job to a whitelist when its egress address becomes something you own rather than something you inherit.

    Create credentials at signup, or talk to support about whitelisting if your production egress is already pinned to a fixed address.