如果您管理服务器、编写自动化脚本或在企业基础设施中部署应用程序——您迟早会遇到需要通过代理转发 curl 或 wget 流量的情况。这可能是企业代理、绕过地理限制下载包,或在对外 API 的大量请求中轮换 IP。在本文中——只有实践:命令、配置、代码示例,没有废话。
1. curl 和 wget 如何与代理工作:基本机制
在深入配置之前,重要的是要理解底层发生了什么。这两个工具支持两种主要的代理协议:HTTP/HTTPS 和 SOCKS5。它们的机制不同,这影响了在特定任务中选择哪种类型的代理。
HTTP 代理 作为应用层协议的中介工作。当 curl 通过 HTTP 代理发送请求时,它实际上是在告诉代理服务器:“替我发起一个 GET 请求到这个 URL。”对于 HTTPS 流量,使用 CONNECT 方法——curl 请求代理建立到目标主机的隧道,然后 TLS 握手直接在客户端和目标服务器之间进行。这一点很重要:在这种情况下,代理无法看到 HTTPS 流量的内容。
SOCKS5 在更低的层次上工作——它代理 TCP/UDP 连接,而不依赖于应用层协议。这使得 SOCKS5 更加通用:它不仅可以转发 HTTP/HTTPS,还可以处理其他协议。此外,SOCKS5 支持在代理服务器端进行 DNS 解析——这对于防止 DNS 泄漏至关重要。
💡 实践中的关键区别:
如果只是需要下载文件或进行 API 请求——HTTP 代理就足够了。如果需要完全匿名、绕过 DNS 泄漏或处理非标准协议——请使用 SOCKS5。
curl 从非常早期的版本开始就原生支持这两种协议。wget 历史上对 SOCKS5 的支持较为有限——在旧版本中(1.19 之前)根本没有 SOCKS5 的支持,只有 HTTP 代理。在编写需要在不同发行版上运行的脚本时,这一点需要考虑。
可以使用命令 wget --version 检查 wget 的版本。对于 curl,可以使用 curl --version,在那里也可以看到 libcurl 库是用哪些协议构建的。
2. 环境变量:最快的设置方法
为 curl 和 wget 设置代理的最优雅方法是通过环境变量。这是系统级的:不需要更改每个脚本,只需在会话中设置一次变量或将其添加到用户配置文件中即可。
这两个工具读取相同的标准变量:
# 对于 HTTP 流量 export http_proxy="http://proxy.example.com:3128" # 对于 HTTPS 流量 export https_proxy="http://proxy.example.com:3128" # 在大写中重复(某些程序仅读取它们) export HTTP_PROXY="http://proxy.example.com:3128" export HTTPS_PROXY="http://proxy.example.com:3128" # 对于 FTP(如果需要) export ftp_proxy="http://proxy.example.com:3128" # 排除项——不通过代理的地址 export no_proxy="localhost,127.0.0.1,::1,192.168.0.0/16,.internal.company.com"
重要的细节:curl 对变量的大小写敏感,具体取决于版本。为了避免意外——始终设置两个版本:小写和大写。这是 DevOps 中的标准实践。
要使设置对特定用户持续有效,请将行添加到 ~/.bashrc 或 ~/.profile。对于系统脚本和服务——在 /etc/environment 中或通过 Environment= 指令在 systemd 单元文件中。
# /etc/systemd/system/myservice.service
[Service]
Environment="http_proxy=http://proxy.example.com:3128"
Environment="https_proxy=http://proxy.example.com:3128"
Environment="no_proxy=localhost,127.0.0.1"
ExecStart=/usr/bin/myapp
如果代理需要身份验证,用户名和密码直接插入变量的 URL 中:
export http_proxy="http://username:[email protected]:3128" export https_proxy="http://username:[email protected]:3128"
⚠️ 安全性:
不要在生产服务器的 .bashrc 或环境变量中以明文形式存储密码。使用保险库解决方案(HashiCorp Vault、AWS Secrets Manager)或至少限制对包含变量的文件的访问权限。
3. curl 的代理标志:完整解析
curl 提供了一组丰富的标志,用于直接从命令行管理代理。这在需要通过代理进行一次性请求而不更改系统设置时非常方便。
主要标志是 -x 或 --proxy:
# 基本 HTTP 代理 curl -x http://proxy.example.com:3128 https://api.example.com/data # 简短形式 curl -x proxy.example.com:3128 https://api.example.com/data # 显式指定协议 curl --proxy http://proxy.example.com:3128 https://api.example.com/data # 通过代理检查外部 IP curl -x http://proxy.example.com:3128 https://api.ipify.org
要忽略环境变量并直接连接(绕过已配置的代理),使用标志 --noproxy:
# 忽略特定主机的代理 curl --noproxy "internal.company.com" https://internal.company.com/api # 完全忽略代理(即使设置了环境变量) curl --noproxy "*" https://api.example.com/data
用于调试代理连接的有用标志:
# 详细模式:可以看到所有头信息,包括与代理的 CONNECT curl -v -x http://proxy.example.com:3128 https://api.example.com/data # 仅响应头 curl -I -x http://proxy.example.com:3128 https://api.example.com/data # 显示通过代理的连接时间 curl -w "Connect: %{time_connect}s\nTotal: %{time_total}s\n" \ -x http://proxy.example.com:3128 \ -o /dev/null -s https://api.example.com/data
通过配置文件 ~/.curlrc 设置代理——如果不想每次都输入标志,这很方便:
# ~/.curlrc
proxy = http://proxy.example.com:3128
proxy-user = username:password
noproxy = localhost,127.0.0.1,.internal.company.com
对于系统脚本,可以创建单独的配置,并通过 curl -K /path/to/config 显式指定——这允许为不同任务拥有不同的代理配置文件。
4. 在 wget 中设置代理:标志和配置文件
wget 的灵活性不如 curl,但对于典型任务——下载文件、递归镜像网站——它的功能完全足够。可以通过三种方式在 wget 中设置代理:通过环境变量(上面讨论过)、通过命令行标志和通过配置文件 ~/.wgetrc。
wget 的命令行标志用于代理:
# 通过标志设置 HTTP 代理 wget -e use_proxy=yes \ -e http_proxy=http://proxy.example.com:3128 \ https://example.com/file.tar.gz # 通过代理设置 HTTPS wget -e use_proxy=yes \ -e https_proxy=http://proxy.example.com:3128 \ https://example.com/file.tar.gz # 带身份验证 wget -e use_proxy=yes \ -e http_proxy=http://username:[email protected]:3128 \ https://example.com/file.tar.gz # 对于特定命令禁用代理(如果设置了 env 变量) wget --no-proxy https://internal.company.com/file.tar.gz
配置文件 ~/.wgetrc 是持续设置的首选方式:
# ~/.wgetrc use_proxy = on http_proxy = http://proxy.example.com:3128 https_proxy = http://proxy.example.com:3128 ftp_proxy = http://proxy.example.com:3128 no_proxy = localhost,127.0.0.1,.internal.company.com # 如果代理需要身份验证 proxy_user = username proxy_password = secretpassword
对于系统应用(所有用户),使用 /etc/wgetrc——格式相同,但全局应用。适用于所有下载操作必须通过企业代理的服务器。
实用示例:通过代理递归下载网站,限制深度和速度:
wget -e use_proxy=yes \
-e http_proxy=http://proxy.example.com:3128 \
--recursive \
--level=2 \
--limit-rate=500k \
--wait=1 \
--random-wait \
--user-agent="Mozilla/5.0 (compatible; Googlebot/2.1)" \
https://example.com/docs/
5. curl 和 wget 中的 SOCKS5 代理:设置和示例
SOCKS5 是处理匿名性或使用非标准端口任务的更优选协议。对于系统管理员和 DevOps 工程师,SOCKS5 通常在通过 SSH 隧道工作时使用,以及在连接到 住宅代理 时,这些代理模拟真实用户的流量。
在 curl 中,SOCKS5 通过代理 URL 中的特殊前缀支持:
# SOCKS5 在客户端进行 DNS 解析(可能会有 DNS 泄漏!) curl --socks5 proxy.example.com:1080 https://api.example.com/data # SOCKS5 在代理端进行 DNS 解析(推荐!) curl --socks5-hostname proxy.example.com:1080 https://api.example.com/data # 通过 -x 标志显式指定协议 curl -x socks5h://proxy.example.com:1080 https://api.example.com/data # 带身份验证 curl -x socks5h://username:[email protected]:1080 https://api.example.com/data # 通过环境变量使用 SOCKS5 export all_proxy="socks5h://proxy.example.com:1080" curl https://api.example.com/data
📌 socks5 与 socks5h——有什么区别?
socks5——DNS 查询在本地执行,通过代理仅进行 TCP 连接。可能会有 DNS 泄漏。socks5h(h = 主机名)——DNS 查询在代理服务器端执行。完全匿名。推荐用于大多数任务。
DevOps 中的一个流行场景是使用 SSH 作为 SOCKS5 代理,通过堡垒主机隧道流量:
# 在本地端口 1080 上打开带 SOCKS5 的 SSH 隧道 ssh -D 1080 -f -C -q -N [email protected] # 现在在 curl 中使用这个隧道 curl -x socks5h://localhost:1080 https://internal-api.private.network/data # 或通过环境变量为会话中的所有请求使用 export all_proxy="socks5h://localhost:1080" wget https://internal-resource.private.network/file.tar.gz
wget 从版本 1.19 开始支持 SOCKS5。在旧版本(CentOS 7、Ubuntu 16.04)中,您需要使用替代解决方案:proxychains、tsocks,或者在需要 SOCKS5 的任务中切换到 curl。
6. 代理身份验证:用户名和密码
大多数商业代理和企业代理服务器都需要身份验证。有几种方法可以传递凭据——每种方法在安全性方面都有其优缺点。
方法 1:在 URL 中提供凭据——简单,但不安全(密码在进程列表中可见):
curl -x http://user:p%[email protected]:3128 https://api.example.com/data # 密码中的特殊字符需要进行 URL 编码:@ → %40, : → %3A
方法 2:curl 中的 --proxy-user 标志——可以不在命令行中指定密码,curl 会交互式地请求它:
# 在标志中提供用户名和密码(仍然在 ps aux 中可见) curl -x http://proxy.example.com:3128 \ --proxy-user "username:password" \ https://api.example.com/data # 仅提供用户名——curl 会交互式地询问密码 curl -x http://proxy.example.com:3128 \ --proxy-user "username" \ https://api.example.com/data
方法 3:通过 .netrc 文件——对于脚本来说是最安全的:
# ~/.netrc machine proxy.example.com login username password secretpassword # 限制对文件的访问权限 chmod 600 ~/.netrc # 在 curl 中使用 curl -x http://proxy.example.com:3128 --proxy-netrc https://api.example.com/data
方法 4:通过秘密存储中的环境变量——推荐用于 CI/CD 和生产环境:
# 在脚本中从变量中读取凭据(由 CI/CD 设置)
#!/bin/bash
PROXY_URL="http://${PROXY_USER}:${PROXY_PASS}@proxy.example.com:3128"
curl -x "${PROXY_URL}" https://api.example.com/data
身份验证方法的比较表:
| 方法 | 便利性 | 安全性 | 适合于 |
|---|---|---|---|
URL (user:pass@host) |
⭐⭐⭐ | ⭐ | 测试 |
| --proxy-user 标志 | ⭐⭐⭐ | ⭐⭐ | 一次性命令 |
| .netrc 文件 | ⭐⭐ | ⭐⭐⭐ | 本地脚本 |
| 来自保险库的环境变量 | ⭐⭐ | ⭐⭐⭐⭐⭐ | CI/CD,生产 |
7. 排除和 no_proxy:如何绕过本地地址的代理
在企业和云环境中,通常需要精细的设置:外部流量——通过代理,内部流量——直接。变量 no_proxy(或 NO_PROXY)允许指定排除列表。
# 基本排除项 export no_proxy="localhost,127.0.0.1,::1" # 按域名排除(带点——所有子域) export no_proxy="localhost,127.0.0.1,.internal.company.com,.corp.local" # 按 IP 范围排除(CIDR 表示法并非在所有地方都有效!) export no_proxy="localhost,127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" # 对于 AWS:排除元数据端点和内部地址 export no_proxy="localhost,127.0.0.1,169.254.169.254,.amazonaws.com.internal"
⚠️ no_proxy 的重要特性:
CIDR 表示法(10.0.0.0/8)并非所有工具都支持。curl 从版本 7.86.0 开始支持。wget——根本不支持。为了兼容性,最好列出具体的 IP 或掩码,如 10.(所有以 10. 开头的)。
针对 Kubernetes 环境的实用示例,需要排除 API 服务器和内部服务:
export no_proxy="localhost,127.0.0.1,10.96.0.0/12,10.244.0.0/16,.cluster.local,.svc,.default"
export NO_PROXY="${no_proxy}"
8. CI/CD 中的代理:GitHub Actions、GitLab CI、Docker
在 CI/CD 管道中设置代理是 DevOps 工程师在企业网络或有限互联网访问情况下最常见的任务之一。我们将讨论流行平台的具体配置。
GitHub Actions
# .github/workflows/deploy.yml
name: Deploy
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
env:
http_proxy: ${{ secrets.PROXY_URL }}
https_proxy: ${{ secrets.PROXY_URL }}
no_proxy: "localhost,127.0.0.1,.github.com"
steps:
- uses: actions/checkout@v3
- name: 下载依赖
run: |
curl -x "${http_proxy}" https://external-api.example.com/config.json -o config.json
wget -e use_proxy=yes -e http_proxy="${http_proxy}" https://releases.example.com/app-v1.0.tar.gz
GitLab CI
# .gitlab-ci.yml
variables:
http_proxy: "http://proxy.company.com:3128"
https_proxy: "http://proxy.company.com:3128"
no_proxy: "localhost,127.0.0.1,.gitlab.company.com"
build:
stage: build
script:
- curl -x "${http_proxy}" https://registry.npmjs.org/package -o package.json
- wget -e use_proxy=yes -e http_proxy="${http_proxy}" https://example.com/resource.tar.gz
Docker:构建镜像时的代理
# 通过构建参数传递代理 docker build \ --build-arg http_proxy=http://proxy.example.com:3128 \ --build-arg https_proxy=http://proxy.example.com:3128 \ --build-arg no_proxy=localhost,127.0.0.1 \ -t myapp:latest . # 在 Dockerfile 中使用 ARG 获取变量
# Dockerfile FROM ubuntu:22.04 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 wget RUN curl -x "${http_proxy}" https://example.com/setup.sh | bash # 在最终镜像中清除代理变量(可选) ENV http_proxy="" ENV https_proxy=""
Docker 守护进程的全局代理设置(通过代理进行 docker pull):
# /etc/systemd/system/docker.service.d/proxy.conf [Service] Environment="HTTP_PROXY=http://proxy.example.com:3128" Environment="HTTPS_PROXY=http://proxy.example.com:3128" Environment="NO_PROXY=localhost,127.0.0.1,.internal.registry.com" # 重启 docker systemctl daemon-reload systemctl restart docker
9. 在 bash 脚本中轮换代理
如果您需要向外部 API 或服务发出大量请求——代理轮换可以分散负载并避免 IP 被封锁。这在价格监控、数据收集或从不同地区测试资源可用性时尤为重要。
对于这些任务,数据中心代理 非常合适——它们在大量请求时提供高速度和稳定性。
#!/bin/bash # rotate_proxy.sh — 从列表中轮换代理 PROXY_LIST=( "http://user:[email protected]:3128" "http://user:[email protected]:3128" "http://user:[email protected]:3128" "http://user:[email protected]:3128" ) URLS_FILE="urls.txt" OUTPUT_DIR="./results" mkdir -p "${OUTPUT_DIR}" PROXY_COUNT=${#PROXY_LIST[@]} INDEX=0 while IFS= read -r url; do PROXY="${PROXY_LIST[$INDEX]}" FILENAME=$(echo "${url}" | md5sum | cut -d' ' -f1) echo "请求:${url} 通过代理 $((INDEX + 1))/${PROXY_COUNT}" curl -x "${PROXY}" \ --max-time 30 \ --retry 3 \ --retry-delay 2 \ --silent \ --output "${OUTPUT_DIR}/${FILENAME}.html" \ "${url}" if [ $? -eq 0 ]; then echo " ✓ 成功" else echo " ✗ 失败,尝试下一个代理..." INDEX=$(( (INDEX + 1) % PROXY_COUNT )) curl -x "${PROXY_LIST[$INDEX]}" \ --max-time 30 \ --silent \ --output "${OUTPUT_DIR}/${FILENAME}.html" \ "${url}" fi # 转到下一个代理 INDEX=$(( (INDEX + 1) % PROXY_COUNT )) # 请求之间稍作暂停 sleep 0.5 done < "${URLS_FILE}" echo "完成!结果已保存到 ${OUTPUT_DIR}"
更高级的选项——在使用之前检查代理的可用性:
#!/bin/bash # check_proxy.sh — 检查代理的可用性 check_proxy() { local proxy_url="$1" local test_url="https://api.ipify.org" result=$(curl -x "${proxy_url}" \ --max-time 10 \ --silent \ --write-out "%{http_code}" \ --output /dev/null \ "${test_url}") if [ "${result}" -eq 200 ]; then echo "存活" else echo "死亡" fi } # 通过代理获取外部 IP get_proxy_ip() { local proxy_url="$1" curl -x "${proxy_url}" --max-time 10 --silent https://api.ipify.org } PROXY="http://user:[email protected]:3128" STATUS=$(check_proxy "${PROXY}") echo "代理状态:${STATUS}" if [ "${STATUS}" == "存活" ]; then EXTERNAL_IP=$(get_proxy_ip "${PROXY}") echo "通过代理获取的外部 IP:${EXTERNAL_IP}" fi
10. 故障排除和常见错误
使用代理不可避免地会遇到错误——尤其是在初始设置时。我们将讨论最常见的问题及其诊断方法。
通过详细模式进行诊断
# curl 的详细输出 curl -vvv -x http://proxy.example.com:3128 https://api.example.com/data 2>&1 | head -50 # 查看输出中的内容: # * 连接到 proxy.example.com — 与代理的连接已建立 # CONNECT api.example.com:443 — 请求隧道 # HTTP/1.1 200 连接已建立 — 隧道已打开 # * SSL 连接使用 TLS — TLS 正在工作 # 调试 wget wget -d -e use_proxy=yes -e http_proxy=http://proxy.example.com:3128 https://api.example.com/data
常见错误及解决方案
| 错误 | 原因 | 解决方案 |
|---|---|---|
407 代理身份验证失败 |
未提供凭据 | 在代理 URL 中添加 user:pass 或使用 --proxy-user 标志 |
连接被拒绝 |
端口错误或代理不可用 | 检查端口:nc -zv proxy.host 3128 |
SSL 证书错误 |
企业代理带有 SSL 检查 | 添加企业 CA:--cacert /path/to/ca.crt |
无法解析代理 |
DNS 无法解析代理名称 | 使用 IP 而不是名称或检查 DNS |
超时 |
代理缓慢或过载 | 增加超时:--max-time |