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

  1. Open your order in the dashboard at https://app.proxyhive.io.
  2. 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.
  3. 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 setNet::HTTPHTTPartyFaraday
http_proxy onlyuses ituses itgoes direct
https_proxy onlygoes directgoes directuses it
HTTP_PROXY onlyuses it, with a warninguses it, with a warninggoes 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

ErrorCauseFix
Net::HTTPClientException: 407 "Proxy Authentication Required"Wrong credentialsRe-copy them from the order
Faraday::ConnectionFailed with a 407 messageSame, through FaradaySame
Traffic leaves from your own IPOnly https_proxy set for Net::HTTP or HTTPartySet http_proxy too, or pass the proxy explicitly
Errno::ECONNREFUSED: Failed to open TCP connectionWrong host or portRe-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