A Ruby proxy for Net::HTTP is four extra arguments: Net::HTTP.new(host, port, proxy_host, proxy_port, proxy_user, proxy_pass). Faraday takes a proxy: URL on the connection, and HTTParty takes http_proxyaddr, http_proxyport, http_proxyuser and http_proxypass. All three open a CONNECT tunnel for HTTPS sites with your credentials on it. The one surprise is the environment: Net::HTTP reads http_proxy even for HTTPS URLs and never reads https_proxy.
Every snippet on this page ran on 2026-09-30 with Ruby 3.3.12, net-http 0.4.1 and 0.9.1, Faraday 2.14.4 (faraday-net_http 3.4.4), HTTParty 0.24.2 and httpx 1.8.4, against local authenticating HTTP proxies and a SOCKS5 proxy that enforces username and password.
Before you start: copy your proxy details
- Open your order in the dashboard at https://app.proxyhive.io.
- Copy HOST, PORT, USERNAME and PASSWORD. Every ISP or datacenter IP is its own endpoint, with separate HTTP, HTTPS and SOCKS5 ports on the order. Use the HTTP port for Net::HTTP, Faraday and HTTParty.
- Export them:
export PROXY_HOST=HOST PROXY_PORT=PORT PROXY_USER=USERNAME PROXY_PASS=PASSWORD
No address yet? Static ISP proxies start at a single IP.
Ruby proxy with Net::HTTP and authentication
require "net/http"
uri = URI("https://api.ipify.org?format=json")
Net::HTTP.start(
uri.host, uri.port,
ENV.fetch("PROXY_HOST"), Integer(ENV.fetch("PROXY_PORT")),
ENV.fetch("PROXY_USER"), ENV.fetch("PROXY_PASS"),
use_ssl: true, open_timeout: 10, read_timeout: 30
) do |http|
response = http.get(uri.request_uri)
puts "#{response.code} #{response.body}"
end
Net::HTTP.new takes the same six positional arguments if you prefer to set use_ssl, open_timeout and read_timeout as attributes afterwards. The Net::HTTP documentation calls them p_addr, p_port, p_user and p_pass. Net::HTTP sent the credentials on the first CONNECT in our proxy log, with no 407 round trip. A wrong password raises Net::HTTPClientException: 407 "Proxy Authentication Required" when start opens the tunnel.
The http_proxy trap: which variable each library reads
When you pass no proxy, each library falls back to the environment, and they disagree. We set one variable at a time, with a deliberately wrong password so that a 407 proved the proxy was used, and requested an HTTPS URL:
| Variable set | Net::HTTP | HTTParty | Faraday |
|---|---|---|---|
http_proxy only | uses it | uses it | goes direct |
https_proxy only | goes direct | goes direct | uses it |
HTTP_PROXY only | uses it, with a warning | uses it, with a warning | goes direct |
Net::HTTP looks up the proxy as if the target were http://, so https_proxy is invisible to it. HTTParty builds on Net::HTTP and inherits that. Faraday chooses by the real scheme, like cURL and Python requests. In a container where only https_proxy is set, a Net::HTTP client quietly sends your traffic from the host's own IP. Set both variables, or pass the proxy explicitly, which is what the rest of this page does.
Faraday proxy
require "faraday"
proxy = "http://#{ENV.fetch('PROXY_USER')}:#{ENV.fetch('PROXY_PASS')}@#{ENV.fetch('PROXY_HOST')}:#{ENV.fetch('PROXY_PORT')}"
conn = Faraday.new(
url: "https://api.ipify.org",
proxy: proxy,
request: { open_timeout: 10, timeout: 30 }
)
response = conn.get("/", format: "json")
puts "#{response.status} #{response.body}"
The proxy belongs to the connection, so keep one Faraday::Connection per proxy endpoint. With the default net_http adapter, a 407 surfaces as Faraday::ConnectionFailed. The Faraday proxy options page covers the hash form, which takes uri, user and password separately.
HTTParty proxy options
require "httparty"
response = HTTParty.get(
"https://api.ipify.org?format=json",
http_proxyaddr: ENV.fetch("PROXY_HOST"),
http_proxyport: Integer(ENV.fetch("PROXY_PORT")),
http_proxyuser: ENV.fetch("PROXY_USER"),
http_proxypass: ENV.fetch("PROXY_PASS"),
timeout: 30
)
puts "#{response.code} #{response.body}"
In a class that include HTTParty, the class-level http_proxy "HOST", PORT, "USERNAME", "PASSWORD" sets the same thing for every request the class makes.
Ruby SOCKS5 proxy with httpx
Net::HTTP has no SOCKS support. The httpx gem does, with credentials in the URL:
require "httpx"
socks = "socks5://#{ENV.fetch('PROXY_USER')}:#{ENV.fetch('PROXY_PASS')}@#{ENV.fetch('PROXY_HOST')}:#{ENV.fetch('SOCKS5_PORT')}"
response = HTTPX.plugin(:proxy).with_proxy(uri: socks).get("https://api.ipify.org?format=json")
response.raise_for_status
puts "#{response.status} #{response.body}"
Use the SOCKS5 port from your order for SOCKS5_PORT. Our SOCKS server received the hostname, not an IP, so DNS resolved on the proxy side. If SOCKS5 is new to you, HTTP vs SOCKS5 proxies explains when it is worth the extra gem.
Rotate across several static IPs
With several ISP or datacenter IPs, cycle through their endpoints from a comma-separated PROXY_URLS:
require "net/http"
endpoints = ENV.fetch("PROXY_URLS").split(",").map { |u| URI(u) }.cycle
uri = URI("https://api.ipify.org?format=json")
4.times do
proxy = endpoints.next
Net::HTTP.start(uri.host, uri.port, proxy.host, proxy.port,
URI.decode_uri_component(proxy.user), URI.decode_uri_component(proxy.password),
use_ssl: true, open_timeout: 10, read_timeout: 30) do |http|
puts "#{proxy.port} #{http.get(uri.request_uri).body}"
end
end
Each entry is http://USER:PASS@HOST:PORT, percent-encoded if the password has special characters; decode_uri_component undoes that. This opens a new tunnel per request, which is simple and costs a handshake each time. For throughput, keep one started Net::HTTP object per endpoint and reuse it. ISP and datacenter addresses are static for the term, so rotation is your choice of order and nothing else.
Common errors
| Error | Cause | Fix |
|---|---|---|
Net::HTTPClientException: 407 "Proxy Authentication Required" | Wrong credentials | Re-copy them from the order |
Faraday::ConnectionFailed with a 407 message | Same, through Faraday | Same |
| Traffic leaves from your own IP | Only https_proxy set for Net::HTTP or HTTParty | Set http_proxy too, or pass the proxy explicitly |
Errno::ECONNREFUSED: Failed to open TCP connection | Wrong host or port | Re-copy the HTTP port; test it with cURL |
For status codes the target sends back through a working tunnel, see proxy error codes.
Next steps
- The Python equivalent: Python requests.
- Line rates on ISP pricing and connection details in the docs.