Is your pipeline failing with a 403 Forbidden or Connection refused error when trying to access an external API or download a dependency? Most likely, the issue is that the IP address of your CI/CD server is blocked on the resource side. A proxy solves this problem: you route traffic through the desired IP, and the pipeline runs smoothly. This article provides step-by-step instructions for GitHub Actions, GitLab CI, and Jenkins.
Why use a proxy in CI/CD: real scenarios
CI/CD pipelines run on servers with fixed IP addresses ā cloud runners from GitHub, GitLab, or on self-hosted Jenkins agents. These IPs are well-known, and many external services either block them or limit the number of requests. Here are specific situations where a proxy is essential:
Access to geo-restricted resources
Many corporate npm registries, Maven repositories, and internal APIs are only accessible from certain countries or IP ranges. If your GitHub Actions runner is located in a region that is blocked at the firewall level of the target service, the pipeline will not be able to download dependencies or send data. A proxy with the required geolocation solves this issue without changing the infrastructure.
Rate limiting and IP blocking
GitHub Actions cloud runners use IPs from Microsoft Azure ranges. Many public APIs are aware of these ranges and impose strict limits ā or block them entirely. For example, scraping public data, making requests to third-party APIs during tests, downloading distributions from restricted CDNs ā all these tasks frequently fail due to the IPs of cloud runners. Rotating through a proxy allows you to bypass rate limiting.
Integration testing with real websites
If your integration tests access real websites or marketplaces (Wildberries, Ozon, Avito, Amazon), these sites see the same IP from the runner on each run and quickly block it. A rotating IP proxy allows tests to pass consistently without CAPTCHAs and blocks.
Access to internal corporate resources
Corporate networks are often closed off from the outside world. If the pipeline needs to deploy to an internal server or access a closed API, a proxy (or SOCKS5 tunnel) within the corporate network becomes a bridge between the cloud runner and the closed infrastructure.
Testing advertising and marketing integrations
Teams working with Facebook Ads API, TikTok Ads API, or Google Ads API often automate campaign creation through CI/CD. These platforms have strict IP rules: requests from data center IPs may be blocked or require additional verification. Residential proxies in the pipeline make requests appear like regular user traffic.
Which type of proxy to choose for the pipeline
The choice of proxy type depends on the task. For CI/CD pipelines, three options are relevant ā each with its own advantages and limitations:
| Proxy Type | Speed | Trust by sites | Best for |
|---|---|---|---|
| Datacenter proxies | Very high | Medium | Downloading dependencies, internal repositories, fast APIs without strict checks |
| Residential proxies | Medium | High | Integration tests with real websites, advertising APIs (Facebook, TikTok), marketplaces |
| Mobile proxies | Medium | Maximum | Testing mobile APIs, working with platforms with maximum anti-bot protections |
Practical rule:
If the task is to download packages or access an internal service, use datacenter proxies ā they are fast and cheaper. If the task involves tests on real sites or working with advertising platforms, residential proxies are needed. The SOCKS5 protocol is preferable to HTTP/HTTPS, as it works more transparently with non-standard ports and protocols.
Configuring a proxy in GitHub Actions
GitHub Actions is the most popular CI/CD tool today. Configuring a proxy here is done through environment variables and repository secrets. Let's break it down step by step.
Step 1: Add proxy data to repository secrets
Never write the proxy username and password directly in the YAML workflow file. Use GitHub Secrets:
- Open the repository ā Settings ā Secrets and variables ā Actions
- Click New repository secret
- Create a secret
PROXY_URLwith a value likehttp://user:[email protected]:port
Step 2: Use environment variables in the workflow
Most tools (curl, wget, npm, pip, Maven) automatically pick up standard environment variables HTTP_PROXY, HTTPS_PROXY, and NO_PROXY. Example workflow:
name: Build with Proxy
on: [push]
env:
HTTP_PROXY: ${{ secrets.PROXY_URL }}
HTTPS_PROXY: ${{ secrets.PROXY_URL }}
NO_PROXY: localhost,127.0.0.1,internal.company.com
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Run integration tests
run: npm test
- name: Call external API
run: |
curl -v https://api.example.com/data
Step 3: SOCKS5 proxy in GitHub Actions
If you are using SOCKS5 (recommended for most tasks), standard environment variables are not enough ā a local tunnel is needed. Use the proxychains utility or set up microsocks:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Setup SOCKS5 proxy tunnel
run: |
sudo apt-get install -y proxychains4
echo "socks5 proxy.host 1080 user password" >> /etc/proxychains4.conf
- name: Run command through SOCKS5
run: proxychains4 curl https://restricted-resource.com/api
Configuring a proxy for specific tools
Some tools ignore system variables and require separate configuration:
| Tool | How to configure the proxy |
|---|---|
| npm / yarn | npm config set proxy http://user:pass@host:port |
| pip (Python) | pip install --proxy http://user:pass@host:port package |
| Maven | Through settings.xml section <proxies> |
| Gradle | systemProp.https.proxyHost=host in gradle.properties |
| Git | git config --global http.proxy http://user:pass@host:port |
| Docker build | --build-arg HTTP_PROXY=http://user:pass@host:port |
Configuring a proxy in GitLab CI
GitLab CI provides several levels for setting environment variables: at the project, group, or instance level. This makes proxy management more flexible compared to GitHub Actions.
Step 1: Add variables in GitLab CI/CD Variables
- Open the project ā Settings ā CI/CD ā section Variables
- Click Add variable
- Add the variable
PROXY_URLwith the type Masked (hides the value in logs) - Value:
http://user:[email protected]:port
Step 2: Use variables in .gitlab-ci.yml
variables:
HTTP_PROXY: $PROXY_URL
HTTPS_PROXY: $PROXY_URL
NO_PROXY: "localhost,127.0.0.1,.internal.company.com"
stages:
- build
- test
- deploy
build:
stage: build
image: node:20-alpine
script:
- npm ci
- npm run build
integration_tests:
stage: test
image: python:3.11
script:
- pip install -r requirements.txt
- pytest tests/integration/
deploy:
stage: deploy
script:
- curl -X POST https://api.external-service.com/deploy
-H "Authorization: Bearer $DEPLOY_TOKEN"
-d '{"version": "$CI_COMMIT_SHA"}'
Proxy only for specific jobs
If the proxy is not needed everywhere (for example, only for integration tests but not for building), set the variables at the specific job level rather than globally:
integration_tests:
stage: test
variables:
HTTP_PROXY: $PROXY_URL
HTTPS_PROXY: $PROXY_URL
script:
- pytest tests/integration/
build:
stage: build
# Here the proxy is not set ā direct connection
script:
- npm ci && npm run build
Self-hosted GitLab Runner: configuring a proxy at the runner level
If you are using your own GitLab Runner, you can set the proxy globally in the runner configuration. Open the file /etc/gitlab-runner/config.toml and add to the [runners.env] section:
[[runners]]
name = "my-runner"
url = "https://gitlab.com/"
token = "TOKEN"
executor = "docker"
environment = [
"HTTP_PROXY=http://user:[email protected]:port",
"HTTPS_PROXY=http://user:[email protected]:port",
"NO_PROXY=localhost,127.0.0.1"
]
This is convenient when all pipelines on this runner need to use the proxy ā there is no need to specify it in every .gitlab-ci.yml.
Configuring a proxy in Jenkins
Jenkins is the most flexible of the three tools, but also the most complex to configure. Proxies can be set at several levels: globally for all Jenkins, for a specific Pipeline, or for an individual step.
Method 1: Global proxy settings in Jenkins
- Open Manage Jenkins ā System
- Find the HTTP Proxy Configuration section
- Fill in the fields: Server, Port, Username, Password
- In the No Proxy Host field, specify internal addresses separated by commas
- Click Test URL to check and save
This setting affects the loading of plugins and updates for Jenkins itself, but does not automatically transfer to the runtime environment of pipelines. A separate configuration is needed for pipelines.
Method 2: Proxy in Declarative Pipeline through environment
pipeline {
agent any
environment {
HTTP_PROXY = credentials('proxy-url-credential')
HTTPS_PROXY = credentials('proxy-url-credential')
NO_PROXY = 'localhost,127.0.0.1,internal.company.com'
}
stages {
stage('Build') {
steps {
sh 'npm ci'
sh 'npm run build'
}
}
stage('Integration Tests') {
steps {
sh 'pytest tests/integration/'
}
}
stage('Deploy') {
steps {
sh '''
curl -X POST https://api.external-service.com/deploy \
-H "Authorization: Bearer ${DEPLOY_TOKEN}" \
-d "version=${GIT_COMMIT}"
'''
}
}
}
}
Step 3: Add proxy credentials in Jenkins Credentials
- Open Manage Jenkins ā Credentials ā System ā Global credentials
- Click Add Credentials
- Type: Secret text
- ID:
proxy-url-credential - Secret:
http://user:[email protected]:port
Method 3: Proxy through JVM parameters for Java projects
If your pipeline builds a Java project (Maven, Gradle), system environment variables may not work ā the JVM uses its own system properties. Add them to JAVA_OPTS:
environment {
JAVA_OPTS = '-Dhttps.proxyHost=proxy.host -Dhttps.proxyPort=8080 -Dhttps.proxyUser=user -Dhttps.proxyPassword=password -Dhttp.nonProxyHosts=localhost|127.0.0.1|*.internal.com'
}
Proxying inside Docker containers in the pipeline
Most modern CI/CD pipelines run steps inside Docker containers. Passing the proxy into the container is a separate task that can be solved in several ways.
Passing the proxy through --build-arg when building the image
If the proxy is only needed during the Docker image build (for example, for installing packages inside the Dockerfile), use build arguments:
# In .github/workflows/build.yml or .gitlab-ci.yml
docker build \
--build-arg HTTP_PROXY=$HTTP_PROXY \
--build-arg HTTPS_PROXY=$HTTPS_PROXY \
--build-arg NO_PROXY=$NO_PROXY \
-t myapp:latest .
# In Dockerfile
ARG HTTP_PROXY
ARG HTTPS_PROXY
ARG NO_PROXY
ENV HTTP_PROXY=$HTTP_PROXY
ENV HTTPS_PROXY=$HTTPS_PROXY
ENV NO_PROXY=$NO_PROXY
RUN apt-get update && apt-get install -y curl
RUN npm ci
ā ļø Important: security when building images
Variables set through ARG and ENV in the Dockerfile are preserved in the image metadata and can be seen via docker inspect. If the proxy requires authentication, ensure that the final image is not published to a public registry ā otherwise, the credentials will be publicly accessible.
Global configuration of Docker daemon for proxy
On self-hosted runners, you can configure the proxy for the entire Docker daemon ā then all containers will automatically receive the proxy without changes in the Dockerfile:
# /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://user:[email protected]:port"
Environment="HTTPS_PROXY=http://user:[email protected]:port"
Environment="NO_PROXY=localhost,127.0.0.1,registry.internal.com"
# Apply changes:
# systemctl daemon-reload
# systemctl restart docker
Security: how to store proxy credentials
Proxy credentials are just as much secrets as API keys or database passwords. Their leakage means that anyone can use your proxy at your expense. Here are the rules for secure storage:
Security checklist
- ā Never write the proxy username/password directly in the pipeline YAML files
- ā Use Masked variables in GitLab and Encrypted secrets in GitHub ā they are hidden in logs
- ā In Jenkins, use the Secret text or Username with password type in the Credentials Store
- ā
Add
NO_PROXYfor internal addresses ā traffic to your infrastructure should not go through the proxy - ā Regularly rotate proxy passwords ā update only in the secrets store without changing the pipeline code
- ā Use IP authentication for the proxy (whitelist the runner's IP) where supported ā this is more reliable than a password
- ā Monitor proxy logs for unusual activity
Proxy URL format: what goes where
| Protocol | URL format | When to use |
|---|---|---|
| HTTP | http://user:pass@host:port |
Most tools, npm, pip, curl |
| HTTPS | https://user:pass@host:port |
Encrypted connection to the proxy server |
| SOCKS5 | socks5://user:pass@host:port |
Non-standard ports, UDP traffic, maximum compatibility |
Common errors and how to fix them
Even after proper configuration, issues may arise. Here are the most common errors and their solutions:
Error: Proxy Authentication Required (407)
Cause: Incorrect username or password, or they are not being passed by the tool.
Solution: Check the URL format ā special characters in the password need to be URL-encoded. For example, p@ss#word ā p%40ss%23word. Also, ensure that the environment variable is actually being passed to the step ā output it using echo $HTTP_PROXY (first few characters) for verification.
Error: SSL Certificate Verification Failed
Cause: The proxy performs SSL inspection (MITM) and replaces the certificate. The client does not trust the proxy's certificate.
Solution: Add the proxy's root certificate to trusted ones. For curl: --cacert /path/to/proxy-ca.crt. For npm: npm config set cafile /path/to/proxy-ca.crt. Alternatively, use a proxy without SSL inspection ā this is preferable for CI/CD.
Error: Connection Timeout through proxy
Cause: The proxy server is inaccessible from the runner's IP, or the port is blocked by a firewall.
Solution: Check the proxy's availability with the command nc -zv proxy.host port in the pipeline step. Ensure that the runner's IP is added to the proxy provider's whitelist (if IP authentication is used). For GitHub Actions cloud runners, IP ranges are published at meta.github.com.
Error: Tool ignores HTTP_PROXY variables
Cause: Some tools (especially Java-based) do not read system environment variables.
Solution: Use native proxy configuration for the specific tool (see the table above). For Java, add JVM properties through JAVA_OPTS. For curl, explicitly use the flag -x http://proxy:port.
Error: Internal services also go through the proxy
Cause: The NO_PROXY variable is not set or is set incorrectly.
Solution: Specify all internal domains and IPs in NO_PROXY. Use wildcards for domains: NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,.internal.company.com. Note: some tools support CIDR notation, while others only support exact domains.
Conclusion
Configuring a proxy in a CI/CD pipeline is not a one-time task, but part of a proper automation architecture. We covered three main tools: GitHub Actions (through Secrets and environment variables), GitLab CI (through Variables with masking), and Jenkins (through Credentials Store and Declarative Pipeline). The key principles are the same for all: never store credentials in code, use NO_PROXY for internal addresses, and choose the type of proxy based on the specific task.
Choosing the right type of proxy is critical for pipeline stability. For downloading dependencies and accessing standard APIs, fast datacenter proxies are sufficient. However, if your pipeline conducts integration testing on real sites, works with advertising platforms (Facebook Ads API, TikTok Ads API), or accesses marketplaces ā use residential proxies: their IPs are perceived as regular user traffic and are rarely subject to blocks or rate limiting.
The main rule: test the proxy as a separate step at the beginning of the pipeline ā this will allow you to quickly diagnose problems and avoid wasting time searching for errors at the end of a long build. Add a step curl -v https://api.ipify.org immediately after setting up the proxy ā it will show the IP from which requests are sent and confirm that the proxying works correctly.