If your K8s cluster makes external requests β to partner APIs, marketplaces, advertising platforms β sooner or later you will encounter IP blocks. The Ingress Controller itself manages incoming traffic, but a separate scheme with a proxy is needed for outgoing requests through pods. In this article, we discuss how to set this up correctly: from choosing the type of proxy to specific YAML configs for NGINX Ingress and Traefik.
Why use a proxy in Kubernetes: real scenarios
Kubernetes is an orchestrator, and most articles about it discuss incoming traffic: how to set up Ingress, how to issue a TLS certificate, how to route requests to services. But there is another side: outgoing traffic from pods. This is where problems with blocks, geo-restrictions, and IP limits arise.
Letβs consider specific tasks where a proxy in a K8s cluster becomes a necessity:
- Price parsing and monitoring. Your cluster runs a CronJob every 15 minutes and collects data from Wildberries, Ozon, Amazon. Without IP rotation, after 2β3 hours you will receive a ban across the entire IP range of your cloud provider.
- Working with advertising APIs. Facebook Marketing API, TikTok Ads API, Google Ads API β all of them track the source of requests. If dozens of pods send requests from one data center IP, this is a trigger for automatic blocking.
- Geolocation testing. Your service must show different content for users from different countries. Through a proxy, pods can simulate requests from the required regions for automated tests.
- Bypassing corporate restrictions. In some infrastructures, all outgoing traffic must go through a corporate proxy β this is a compliance requirement.
- Working with partner APIs with whitelisted IPs. When a partner allows requests only from specific IPs, and your cluster scales and changes addresses β a proxy with static IPs solves the problem.
It is important to understand the architectural separation: The Ingress Controller manages incoming traffic (from users to your services), while the proxy for outgoing requests is a separate task. Nevertheless, they are related: the proxy configuration is often set at the level of the same Ingress Controller or through annotations that it reads.
π‘ Key distinction
In this article, we discuss two scenarios: (1) configuring the Ingress Controller as a reverse proxy to external upstream servers, and (2) routing outgoing traffic from pods through an external proxy server. Both scenarios are encountered in production.
Which type of proxy to choose for a K8s cluster
The choice of proxy type directly affects how long your cluster can operate without blocks. Data center IPs are the cheapest, but they are easily identifiable and blockable. Residential and mobile IPs are more expensive but significantly more reliable for tasks where simulating a real user is important.
| Proxy Type | Speed | Risk of Blocking | Best Scenario in K8s |
|---|---|---|---|
| Data Center | High | High | Internal APIs, corporate compliance, partner integrations with whitelisted IPs |
| Residential | Medium | Low | Parsing marketplaces, working with advertising APIs, geo-testing |
| Mobile | Medium | Minimal | Facebook Ads API, TikTok API, high-load tasks with ban risk |
For most tasks in a K8s cluster related to parsing or working with advertising platforms, the optimal choice is residential proxies with rotation. They provide real IPs of home users, and website protection algorithms perceive such requests as regular browser traffic.
From a protocol perspective, the most universal for K8s is HTTP/HTTPS proxy β it is supported by all popular HTTP clients in any programming language through standard environment variables HTTP_PROXY and HTTPS_PROXY. SOCKS5 is only needed for non-standard protocols or when proxying is required for non-HTTP traffic.
Setting up a proxy through NGINX Ingress Controller
NGINX Ingress Controller is the most common option in K8s. Letβs consider two scenarios: configuring NGINX as a reverse proxy to an external upstream through a proxy server, and global configuration of outgoing proxy for the controller itself.
Annotations for proxying requests to upstream
NGINX Ingress supports annotations to manage proxy behavior. If you need Ingress to forward requests to an external service through an intermediate proxy, use a ConfigMap with a custom config:
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
data:
# Global HTTP proxy for outgoing requests from NGINX
http-snippet: |
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
# Upstream configuration through proxy
use-proxy-protocol: "false"
proxy-real-ip-cidr: "0.0.0.0/0"
Ingress resource with annotations for proxy
For a separate Ingress resource, you can set proxy parameters through annotations. This is useful when different services require different timeouts and settings:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-service-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: "nginx"
# Proxy timeouts
nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
# Buffer size
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
# Forwarding the real client IP
nginx.ingress.kubernetes.io/use-forwarded-headers: "true"
nginx.ingress.kubernetes.io/forwarded-for-header: "X-Forwarded-For"
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
Configuring an external proxy for outgoing requests from the NGINX pod
If the NGINX Ingress Controller itself needs to make outgoing requests through an external proxy (for example, for health-checking external endpoints), you need to set environment variables in the controller's Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
template:
spec:
containers:
- name: controller
image: registry.k8s.io/ingress-nginx/controller:v1.9.4
env:
- name: HTTP_PROXY
valueFrom:
secretKeyRef:
name: proxy-credentials
key: http-proxy-url
- name: HTTPS_PROXY
valueFrom:
secretKeyRef:
name: proxy-credentials
key: https-proxy-url
- name: NO_PROXY
value: "localhost,127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,.cluster.local"
Note the NO_PROXY variable β it is critically important. Without it, all intra-cluster traffic will also go through the external proxy, which will break the interaction between services. Always include RFC-1918 ranges and the suffix .cluster.local in NO_PROXY.
Setting up a proxy in Traefik Ingress Controller
Traefik is a popular alternative to NGINX Ingress, especially in conjunction with Helm and in managed clusters (k3s, Docker Swarm, Nomad). Traefik has its own configuration system through CRD (Custom Resource Definitions) and static/dynamic configs.
Static configuration of Traefik with proxy
apiVersion: v1
kind: ConfigMap
metadata:
name: traefik-config
namespace: traefik
data:
traefik.yaml: |
# Static configuration of Traefik
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
# Settings for working behind a proxy
serversTransport:
insecureSkipVerify: false
maxIdleConnsPerHost: 200
dialTimeout: 10s
responseHeaderTimeout: 30s
# Logging for proxy diagnostics
log:
level: INFO
accessLog:
fields:
headers:
defaultMode: keep
names:
X-Forwarded-For: keep
X-Real-IP: keep
Middleware for adding proxy headers
In Traefik, you can create Middleware that will add or modify headers when passing requests. This is useful for correctly passing the real client IP through the proxy chain:
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: proxy-headers
namespace: production
spec:
headers:
customRequestHeaders:
X-Forwarded-Proto: "https"
# Remove headers that may reveal infrastructure
customResponseHeaders:
X-Powered-By: ""
Server: ""
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: my-app-route
namespace: production
spec:
entryPoints:
- websecure
routes:
- match: Host(`myapp.example.com`)
kind: Rule
middlewares:
- name: proxy-headers
services:
- name: my-service
port: 80
Environment variables for Traefik Deployment
Similarly to NGINX, for Traefik we set environment variables through Helm values or directly in the Deployment. When using the Helm chart, this is done through values.yaml:
# values.yaml for Traefik Helm chart
deployment:
enabled: true
kind: Deployment
env:
- name: HTTP_PROXY
valueFrom:
secretKeyRef:
name: proxy-credentials
key: http-proxy-url
- name: HTTPS_PROXY
valueFrom:
secretKeyRef:
name: proxy-credentials
key: https-proxy-url
- name: NO_PROXY
value: "localhost,127.0.0.1,10.96.0.0/12,10.244.0.0/16,.cluster.local,.svc"
Proxy through environment variables in pods
The most universal way to set up an outgoing proxy for any pod is through standard environment variables. This method works regardless of the programming language and framework: Python requests, Go net/http, Node.js axios, Java HttpClient β all of them automatically read HTTP_PROXY and HTTPS_PROXY.
Option 1: Directly in Pod Spec
apiVersion: v1
kind: Pod
metadata:
name: scraper-pod
namespace: production
spec:
containers:
- name: scraper
image: mycompany/scraper:latest
env:
- name: HTTP_PROXY
value: "http://username:[email protected]:8080"
- name: HTTPS_PROXY
value: "http://username:[email protected]:8080"
- name: NO_PROXY
value: "localhost,127.0.0.1,10.0.0.0/8,.cluster.local"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
β οΈ Do not store credentials in plain text!
The example above is shown for clarity. In production, proxy credentials must always be stored in Kubernetes Secret and passed through secretKeyRef. More details are in the Secrets section below.
Option 2: Through ConfigMap for multiple pods
If the proxy is used by multiple Deployments, it is convenient to move the settings to a ConfigMap and connect it through envFrom:
apiVersion: v1
kind: ConfigMap
metadata:
name: proxy-config
namespace: production
data:
NO_PROXY: "localhost,127.0.0.1,10.0.0.0/8,172.16.0.0/12,.cluster.local,.svc.cluster.local"
PROXY_TIMEOUT: "30"
PROXY_MAX_RETRIES: "3"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: price-monitor
namespace: production
spec:
replicas: 5
selector:
matchLabels:
app: price-monitor
template:
metadata:
labels:
app: price-monitor
spec:
containers:
- name: monitor
image: mycompany/price-monitor:v2.1
envFrom:
- configMapRef:
name: proxy-config
- secretRef:
name: proxy-credentials
ports:
- containerPort: 8080
Option 3: Mutating Admission Webhook
For corporate clusters where all pods must use a proxy, you can set up a mutating admission webhook that automatically adds proxy environment variables to all created pods. This solution is used in enterprise infrastructures where compliance requires that all outgoing traffic goes through a corporate proxy. The implementation of such a webhook goes beyond the scope of this article, but it is worth knowing about its existence.
IP rotation and managing the proxy pool in K8s
A static proxy is good for corporate compliance, but bad for tasks where you need to avoid blocks. If 50 of your pods send requests through the same IP, that IP will be blocked very quickly. The solution is proxy rotation.
Proxy Sidecar Pattern
One of the patterns is to run a proxy agent as a sidecar container next to the main application. The sidecar receives requests on localhost:8080 and forwards them through a rotating pool of external proxies:
apiVersion: apps/v1
kind: Deployment
metadata:
name: scraper-with-proxy-sidecar
namespace: production
spec:
replicas: 10
selector:
matchLabels:
app: scraper
template:
metadata:
labels:
app: scraper
spec:
containers:
# Main application
- name: scraper
image: mycompany/scraper:latest
env:
- name: HTTP_PROXY
value: "http://localhost:8080"
- name: HTTPS_PROXY
value: "http://localhost:8080"
- name: NO_PROXY
value: "localhost,127.0.0.1,.cluster.local"
# Sidecar: local proxy agent with rotation
- name: proxy-rotator
image: mycompany/proxy-rotator:latest
ports:
- containerPort: 8080
env:
- name: PROXY_LIST_URL
valueFrom:
secretKeyRef:
name: proxy-credentials
key: api-endpoint
- name: ROTATION_INTERVAL
value: "60" # rotation every 60 seconds
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
Central Proxy Service Pattern
An alternative approach is a single Proxy Service inside the cluster, which all pods refer to. This is easier to manage: just update one Secret with the proxy credentials, and all pods will automatically start using the new data.
apiVersion: v1
kind: Service
metadata:
name: proxy-gateway
namespace: proxy-system
spec:
selector:
app: proxy-gateway
ports:
- name: http
port: 8080
targetPort: 8080
- name: socks5
port: 1080
targetPort: 1080
type: ClusterIP
---
# Pods use the proxy through internal DNS
# HTTP_PROXY=http://proxy-gateway.proxy-system.svc.cluster.local:8080
For tasks where anonymity is critical and the risk of blocks is minimal (for example, parsing marketplaces or working with advertising APIs), we recommend using residential proxies with rotation β they provide real IPs of home users, making traffic from your cluster indistinguishable from regular browser traffic.
Storing proxy credentials in Kubernetes Secrets
Secure storage of credentials is a mandatory requirement for any production cluster. Never insert proxy username/password directly into YAML manifests that are stored in a Git repository.
Creating a Secret for the proxy
# Create via kubectl (values are automatically base64 encoded)
kubectl create secret generic proxy-credentials \
--namespace=production \
--from-literal=http-proxy-url='http://user:[email protected]:8080' \
--from-literal=https-proxy-url='http://user:[email protected]:8080' \
--from-literal=socks5-proxy-url='socks5://user:[email protected]:1080'
# Check the created Secret
kubectl get secret proxy-credentials -n production -o yaml
YAML manifest of the Secret
apiVersion: v1
kind: Secret
metadata:
name: proxy-credentials
namespace: production
labels:
app: proxy-config
managed-by: ops-team
type: Opaque
# Values are encoded in base64
# echo -n 'http://user:pass@proxy:8080' | base64
stringData:
# stringData allows specifying values in plain text
# Kubernetes automatically encodes them in base64
http-proxy-url: "http://user:[email protected]:8080"
https-proxy-url: "http://user:[email protected]:8080"
proxy-host: "gate.proxycove.com"
proxy-port: "8080"
proxy-username: "user"
proxy-password: "password"
Using Secret in Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-worker
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: api-worker
template:
metadata:
labels:
app: api-worker
spec:
containers:
- name: worker
image: mycompany/api-worker:latest
env:
- name: HTTP_PROXY
valueFrom:
secretKeyRef:
name: proxy-credentials
key: http-proxy-url
- name: HTTPS_PROXY
valueFrom:
secretKeyRef:
name: proxy-credentials
key: https-proxy-url
- name: NO_PROXY
value: "localhost,127.0.0.1,10.0.0.0/8,.cluster.local"
# For applications that read host/port separately
- name: PROXY_HOST
valueFrom:
secretKeyRef:
name: proxy-credentials
key: proxy-host
- name: PROXY_PORT
valueFrom:
secretKeyRef:
name: proxy-credentials
key: proxy-port
Integration with external secret storage
In an enterprise environment, it is recommended to use the External Secrets Operator to synchronize secrets from HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault. This allows for automatic updates of proxy credentials without manual intervention and pod restarts:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: proxy-credentials-ext
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: proxy-credentials
creationPolicy: Owner
data:
- secretKey: http-proxy-url
remoteRef:
key: secret/proxy/proxycove
property: http_proxy_url
- secretKey: https-proxy-url
remoteRef:
key: secret/proxy/proxycove
property: https_proxy_url
Diagnostics and common errors when setting up proxy in K8s
Even with the correct configuration, problems may arise. Letβs discuss the most common ones and ways to diagnose them.
Problem 1: Intra-cluster traffic goes through the proxy
Symptom: pods can no longer see each other, DNS resolution within the cluster breaks, services do not respond. The reason is that the NO_PROXY variable is not set.
The minimally required value of NO_PROXY for a standard K8s cluster:
NO_PROXY=localhost,127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,.cluster.local,.svc,.svc.cluster.local,kubernetes.default.svc
Problem 2: Proxy is not applied to a specific pod
Diagnosis: enter the pod and check the environment variables:
# Check environment variables in the pod
kubectl exec -it <pod-name> -n production -- env | grep -i proxy
# Test connection through the proxy from the pod
kubectl exec -it <pod-name> -n production -- curl -v --proxy http://proxy:8080 https://httpbin.org/ip
# Check that the Secret is mounted correctly
kubectl exec -it <pod-name> -n production -- env | grep PROXY
Problem 3: TLS errors when using HTTPS proxy
If the proxy uses a self-signed certificate or corporate CA, applications will receive TLS verification errors. The solution is to add the proxy CA certificate to the trusted ones:
# Create ConfigMap with CA certificate
kubectl create configmap proxy-ca-cert \
--from-file=proxy-ca.crt=./proxy-ca.crt \
-n production
# Mounting in the pod
spec:
containers:
- name: app
volumeMounts:
- name: proxy-ca
mountPath: /etc/ssl/certs/proxy-ca.crt
subPath: proxy-ca.crt
volumes:
- name: proxy-ca
configMap:
name: proxy-ca-cert
Problem 4: Slow requests through the proxy
If requests through the proxy are significantly slower than direct ones β check:
- DNS resolution: ensure that DNS queries are not going through the proxy (add to
NO_PROXY) - Keep-alive connections: configure a connection pool to the proxy server
- Geographical location: the proxy server should be physically close to the target resource
- Type of proxy: for high-speed tasks, consider data center proxies β they provide minimal latency
Useful commands for diagnostics
# Check logs of Ingress Controller
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx --tail=100
# Check pod events
kubectl describe pod <pod-name> -n production
# Test proxy connection from the pod
kubectl run test-proxy --image=curlimages/curl --rm -it --restart=Never -- \
curl -x http://user:pass@proxy:8080 -v https://httpbin.org/ip
# Check that the Secret exists and contains the necessary keys
kubectl get secret proxy-credentials -n production -o jsonpath='{.data}' | python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
for k, v in data.items():
print(f'{k}: {base64.b64decode(v).decode()[:20]}...')
"
Conclusion and recommendations
Setting up a proxy in Kubernetes is not a one-time task, but part of the cluster's network security architecture. Letβs summarize:
- For NGINX Ingress Controller β use annotations to configure proxy parameters at the level of individual Ingress resources, and environment variables in Deployment for global settings of outgoing traffic.
- For Traefik β combine static configuration through ConfigMap with Middleware for managing headers.
- For pods β standard variables
HTTP_PROXY/HTTPS_PROXYthrough Secrets is the most universal and secure approach. - Always configure NO_PROXY β otherwise, intra-cluster traffic will break.
- Never store credentials for proxies in plain text in manifests or in Git.
- For production β use External Secrets Operator for automatic rotation of credentials.
If your K8s cluster performs tasks where IP stability and minimal risk of blocks are critical β parsing, working with advertising APIs, geo-testing β we recommend using residential proxies. They provide real IPs of home users, which protection algorithms perceive as legitimate traffic. For tasks with high speed requirements and minimal risks of blocks from mobile platforms, mobile proxies are excellent β they operate through real 4G/5G networks of operators.