Back to blog

How to use a proxy with Ruby: configure, authenticate, and rotate with Net::HTTP and Faraday

Share article:

As a Ruby developer, you must have used proxies for multiple applications, including web scraping, API integration, and geo-targeted testing. Without a proxy, every request leaves from the same IP, which is the fastest way to get rate-limited or blocked. In this guide, you'll learn how to configure a Ruby proxy with Net::HTTP and Faraday, add authentication, rotate IPs, and connect Ruby applications to Decodo residential proxies.

Diamond inside a rounded square

TL;DR

  • A Ruby proxy routes your app's requests through an intermediary server, so the target site sees the proxy IP instead of your real one.
  • Net::HTTP ships with Ruby, so no install is needed, while Faraday is a separate gem you add with gem install faraday or via Bundler.
  • Faraday offers middleware, JSON parsing, and adapter options. Proxy settings attach to the connection, not to each request, so use separate connections for different proxies.
  • Rotate IPs to avoid detection. Random selection works for quick runs, but round-robin or weighted rotation distributes traffic better. The article includes a reusable ProxyRotator class with failure tracking and retries.
  • Best practices include setting timeouts on every request, retrying temporary failures with backoff, choosing the proxy type based on the target, using realistic request headers, and switching to browser automation like Playwright or Selenium when an HTTP client isn't enough.
  • Keep credentials out of your code by reading them from environment variables with ENV.fetch, or by setting standard http_proxy and https_proxy variables.

What is a proxy and why use one with Ruby?

proxy server sits between your Ruby application and the website or API you want to access. Instead of your app connecting directly to the target server, it connects to the proxy first. The proxy then forwards the request and returns the response. The target only ever sees the proxy IP, so your real one stays hidden the whole way through.

That extra hop gives you more control over how your requests appear on the web. For Ruby developers, proxies are useful when you need to:

  • Hide your IP address or location
  • Access resources that are blocked by a firewall or network restrictions
  • Reduce IP-based rate limits.
  • Improve performance by caching requests
  • Improve web scraping reliability
  • Access geo-blocked content

For most Ruby projects, you’ll work with HTTP(S) proxies. They’re built for web traffic and work well with common Ruby HTTP clients like Net::HTTP and Faraday.

You may also come across SOCKS proxies. They operate at a lower network level and can route more traffic types, but Ruby’s standard HTTP tooling doesn’t cleanly support SOCKS out of the box. If you want a deeper breakdown, our guide on SOCKS5 vs. HTTP proxy does that very well.

You’ll also need to choose the right proxy type for the job. Datacenter proxies are usually the better fit for fast, simple, high-volume requests where the target doesn’t apply strict traffic checks. Residential proxies are more useful when your Ruby script is dealing with rate limits, geo-restricted pages, or anti-bot systems that look closely at where requests come from.

That’s because residential proxies route traffic through actual user-device IPs, which helps requests look closer to regular web traffic. Mobile and ISP proxies also exist for more specific use cases, but they are also very effective against anti-bot systems.

Setting up your Ruby environment

You don’t need much to follow this tutorial, but you need to know what to install, where to get credentials, and how to pass them into your code securely.

We recommend Ruby 3.0+. You can check your installed version with:

ruby -v

Net::HTTP ships with Ruby by default as a library, so you don’t need to run any extra installs. Faraday is a separate gem, so you’ll have to install it directly. To do this, run:

gem install faraday

This command installs Faraday globally for the active Ruby version on your machine.

For a project setup, you want to use Bundler instead. The Gemfile is the file Bundler uses to track your project's Ruby gem dependencies:

bundle init

Open the generated Gemfile and add Faraday as a project dependency:

gem "faraday"

After saving the Gemfile, install the project dependencies with :First, create a project folder and move into it:

mkdir ruby-proxy
cd ruby-proxy

Then, initialize a new Bundler project to generate a Gemfile

bundle install

Bundler installs the dependencies listed in the Gemfile and records the resolved versions in Gemfile.lock. To keep things simple, create a single Ruby file:

mkdir ruby-proxy-demo
cd ruby-proxy-demo
touch proxy_test.rb

You can test proxy connectivity with an IP echo endpoint to confirm proxy behavior. When the proxy works, the response shows the proxy exit IP and location instead of the local machine IP.

Using a proxy with Net::HTTP

Ruby provides a built-in library called Net::HTTP for making HTTP requests, much like Python’s Requests. It’s a good starting point because it requires no external dependencies. The basic constructor is Net::HTTP.new(host, port, proxy_host, proxy_port).

The first 2 arguments describe the target website, then the third and fourth arguments describe the proxy server.

Here's a really simple example of how you can use a proxy with Net::HTTP:

require "net/http"
require "uri"
target = URI("http://api.ipify.org/")
proxy_host = "proxy.example.com"
proxy_port = 8000
http = Net::HTTP.new(target.host, target.port, proxy_host, proxy_port)
response = http.get(target.path)
puts "Exit IP: #{response.body}"

This script sends a request to an IP echo service through the proxy. The proxy host and port should be replaced with the endpoint supplied by the proxy provider. Every request made through this http instance now travels through that proxy.

If all goes well, you should see a response like:

Exit IP: 45.156.64.0

For HTTPS targets, set use_ssl to true:

require "net/http"
require "uri"
target = URI("https://api.ipify.org/")
http = Net::HTTP.new(target.host, target.port, "proxy.example.com", 8000)
http.use_ssl = true
response = http.get(target.path)
puts "Exit IP: #{response.body}"

This version uses the same proxy pattern, but the target URL starts with https. Setting use_ssl tells Net::HTTP to open a connection tunnel through the proxy and negotiate TLS with the destination server.

Proxies add another network hop, so a slow or unavailable proxy can make a script hang. To mitigate this, we can add timeouts and basic error handling to our script:

require "net/http"
require "uri"
target = URI("https://api.ipify.org/")
http = Net::HTTP.new(target.host, target.port, "proxy.example.com", 8000)
http.use_ssl = true
http.open_timeout = 10
http.read_timeout = 10
begin
response = http.get(target.path)
puts "Status: #{response.code}"
puts "Exit IP: #{response.body}"
rescue Net::OpenTimeout, Net::ReadTimeout => e
warn "Timeout reaching the proxy or target: #{e.message}"
rescue SocketError => e
warn "Connection problem: #{e.message}"
rescue StandardError => e
warn "Request failed: #{e.message}"
end

open_timeout controls how long Ruby waits to establish the connection, while read_timeout controls how long it waits for the response after connecting. The rescue block catches the most common proxy failures and prints a readable message instead of crashing straight away.

Avoid blocks with residential proxies

Access 115M+ ethically-sourced residential proxies across 195 locations. Get past CAPTCHAs, geo-restrictions, and IP bans on infrastructure that delivers a 99.92% success rate and a <0.5s response time.

Configuring authenticated proxies in Ruby

Most proxy providers don't let you connect anonymously. Rather, they require a username and password so they can tie traffic to your account. 

Net::HTTP accepts these as the fifth and sixth arguments to the constructor:

http = Net::HTTP.new(
target_uri.host,
target_uri.port,
proxy_host,
proxy_port,
proxy_user,
proxy_pass
)

This creates an HTTP client instance that sends proxy authentication details when connecting through the proxy. If the credentials are wrong or missing, the proxy may return a 407 Proxy Authentication Required error.

You can also set the credentials after creating the instance with http.proxy_user and http.proxy_pass. Faraday handles authentication differently. You embed the credentials directly in the proxy URL string when you initialize the connection. If there are any special characters in usernames and passwords, they should be URL-encoded before building that URL:

require "faraday"
conn = Faraday.new("https://ip.decodo.com") do |f|
f.proxy = "http://your_username:your_password@gate.decodo.com:7000"
f.response :json
end
response = conn.get
puts response.body

Note that plain credentials should stay outside the source code. This would lead us into the next section, so we can see how to properly use environment variables in our setup.

Using environment variables for proxy configuration

Ruby can read proxy settings from environment variables, which keeps credentials out of your source code. There are two common ways to do this:

The first approach is to store each proxy value separately, then read them inside your Ruby script with ENV.fetch. This works well when you want to pass the proxy credentials directly into Net::HTTP or Faraday.

Set the values in your shell like this:

export PROXY_HOST="proxy.example.com"
export PROXY_PORT="8000"
export PROXY_USER="your_username"
export PROXY_PASS="your_password"

Then read them in Ruby:

proxy_host = ENV.fetch("PROXY_HOST")
proxy_port = ENV.fetch("PROXY_PORT")
proxy_user = ENV.fetch("PROXY_USER")
proxy_pass = ENV.fetch("PROXY_PASS")

The second approach is to use standard proxy environment variables such as http_proxy and https_proxy. To do this, simply define the full proxy URL in the environment and let Ruby’s networking layer discover it.

Set them in your shell like this:

export http_proxy="http://user:password@proxy.example.com:8000"
export https_proxy="http://user:password@proxy.example.com:8000"

With this setup, Net::HTTP can pick up the proxy automatically when using its environment proxy behavior.

A local .env file can also work when paired with a loader such as dotenv, but that file should stay out of version control. In production, use your deployment platform’s secrets or environment variable settings to avoid committing your proxy credentials to the codebase.

There's also a useful companion variable called no_proxy. It accepts hosts that should bypass the proxy, such as localhost127.0.0.1, or internal services.

export no_proxy=localhost,127.0.0.1,internal.mycompany.com

This is handy for keeping internal or local traffic off your metered proxy connection.

Using a proxy with Faraday

Faraday is a flexible Ruby HTTP client with middleware support, JSON parsing, request configuration, and adapter options. Its adapter-based approach means the request code can stay mostly the same even if the underlying HTTP library changes. By default, Faraday uses the Net::HTTP adapter, but Ruby applications can also use adapters such as Excon, Typhoeus, Patron, or EventMachine.

The proxy configuration also stays attached to the Faraday connection, so the higher-level request code remains clean.

require "faraday"
conn = Faraday.new(
"https://ip.decodo.com/json",
proxy: "http://proxy.example.com:8000"
) do |f|
f.response :json
end
response = conn.get
puts response.body

The f.response :json line adds JSON parsing middleware, so response.body comes back as a Ruby hash instead of a raw JSON string.

One important Faraday detail is that proxy settings belong to the connection. They are configured when Faraday.new runs, not on each individual request. If different requests need different proxies, create separate Faraday connections for each proxy.

Note that Faraday is still an HTTP client, not a browser automation tool. So, for websites that require browser rendering or JavaScript execution, using browser automation tools like Playwright or Selenium may be more effective.

Using an authenticated proxy with Faraday

Authenticated proxies work the same way, only that the username and password are added to the proxy connection string.

require "faraday"
proxy_user = "your_username"
proxy_pass = "your_password"
proxy_host = "proxy.example.com"
proxy_port = 8000
proxy_url = "http://#{proxy_user}:#{proxy_pass}@#{proxy_host}:#{proxy_port}"
conn = Faraday.new(
"https://ip.decodo.com/json",
proxy: proxy_url
) do |f|
f.response :json
end
response = conn.get
puts response.body

If your credentials or passwords contain symbols, we recommend encoding them before passing them along to the proxy_url.

Rotating proxies in Ruby to avoid detection

Sending every request through the same IP is a surefire way to get flagged. If every request uses the same address, that IP can hit rate limits, trigger blocks, or return results tied to one location. Anti-bot systems also look at timing and request volume before drawing any conclusions about your request.

IP rotation spreads requests across multiple proxies so one IP doesn’t carry the whole workload. The simplest way to rotate IPs is via random selection. You can put all the proxies in a list, and then randomly choose one before each request:

proxy_list = [
{ host: "10.0.0.1", port: 8000 },
{ host: "10.0.0.2", port: 8000 },
{ host: "10.0.0.3", port: 8000 }
]
chosen = proxy_list.sample

This might be enough for a quick run because it immediately moves traffic away from a single fixed proxy, but it's far from suitable for production use.

The downside is that random selection can choose the same proxy several times in a row or keep using proxies that have already failed, which leaves the rest of the pool underused.

Round-robin rotation guarantees a more even distribution by walking through the list in order. Weighted rotation goes further by preferring proxies with fewer failures or better performance. A good proxy rotator can also remove dead proxies from the pool.

Here is a reusable ProxyRotator class:

class ProxyRotator
def initialize(proxies, strategy: :round_robin)
raise ArgumentError, "proxy list cannot be empty" if proxies.empty?
@proxies = proxies.map { |proxy| { proxy: proxy, failures: 0 } }
@strategy = strategy
@index = 0
@mutex = Mutex.new
end
def next_proxy
@mutex.synchronize do
return nil if @proxies.empty?
case @strategy
when :round_robin
entry = @proxies[@index % @proxies.size]
@index += 1
entry[:proxy]
when :random
@proxies.sample[:proxy]
when :weighted
lowest_failure_count = @proxies.map { |entry| entry[:failures] }.min
healthiest_proxies = @proxies.select { |entry| entry[:failures] == lowest_failure_count }
healthiest_proxies.sample[:proxy]
end
end
end
def report_failure(proxy)
@mutex.synchronize do
entry = @proxies.find { |item| item[:proxy] == proxy }
return unless entry
entry[:failures] += 1
if entry[:failures] >= 3
@proxies.delete(entry)
puts "Removed dead proxy #{proxy[:host]}:#{proxy[:port]}"
end
end
end
def empty?
@mutex.synchronize { @proxies.empty? }
end
end

After setup, next_proxy returns the proxy that should handle the next request. report_failure increments the failure count for a proxy and removes it after 3 failures, so the script stops wasting requests on dead endpoints. The mutex keeps selection and failure tracking consistent when multiple workers share the same rotator.

Rotation also needs pacing. Pass the selected proxy into Net::HTTP or Faraday before sending the request, then wait a short randomized interval before the next one:

sleep(rand(2.0..5.0))

This pauses the script for a random interval between 2 and 5 seconds. Failure handling is the other half of rotation. When a proxy times out or refuses a connection, report the failure to the rotator and retry with another proxy instead of stopping the whole run.

This is a more robust example:

require "net/http"
require "uri"
def fetch_with_retry(url, rotator, max_retries: 3)
target = URI(url)
attempts = 0
begin
attempts += 1
proxy = rotator.next_proxy
raise "No working proxies left" if proxy.nil?
http = Net::HTTP.new(target.host, target.port, proxy[:host], proxy[:port])
http.use_ssl = target.scheme == "https"
http.open_timeout = 10
http.read_timeout = 10
request = Net::HTTP::Get.new(target)
request["User-Agent"] = "Mozilla/5.0 (compatible; RubyScraper/1.0)"
http.request(request)
rescue Net::OpenTimeout, Net::ReadTimeout, SocketError, Errno::ECONNREFUSED => e
rotator.report_failure(proxy) if proxy
if attempts <= max_retries
wait = 2**attempts
puts "Attempt #{attempts} failed with #{e.class}. Retrying in #{wait}s."
sleep(wait)
retry
end
puts "All #{max_retries} retries exhausted."
raise
end
end

This method asks the rotator for a proxy, builds a Net::HTTP request through that proxy, and retries temporary connection failures. Each retry waits longer than the previous one, which reduces pressure on both the proxy and the target. If one proxy keeps failing, report_failure moves it closer to removal from the pool.

Now, managed proxy services reduce that overhead by handling pool quality, rotation, and location routing outside the Ruby application. If you want to compare managed options, check out our guide on the top rotating proxy service providers.

Integrating Decodo proxies with your Ruby application

Decodo gives Ruby teams a cleaner way to use proxies without managing a proxy pool manually. Instead of sourcing and rotating IPs yourself, its infrastructure handles IP selection, rotation, geo-targeting, and pool management for you.

To get started, log in or create an account, and claim your residential proxy free trial to follow along.

Decodo homepage
In the dashboard, select the proxy type and location, then copy the proxy details
Decodo dashboard with Proxy setup tab opened

Now, let’s modify our Net::HTTP setup from earlier to point at Decodo's endpoint with your credentials:

require "json"
require "net/http"
require "uri"
target = URI("https://ip.decodo.com/json")
http = Net::HTTP.new(
target.host,
target.port,
"gate.decodo.com",
7000,
ENV.fetch("DECODO_USER"),
ENV.fetch("DECODO_PASS")
)
http.use_ssl = true
http.open_timeout = 10
http.read_timeout = 10
response = http.get(target.path)
data = JSON.parse(response.body)
puts "Exit IP: #{data["proxy"]["ip"]}"
puts "Country: #{data["country"]["name"]}"
puts "City: #{data["city"]["name"]}"

Note: Replace these placeholder values with your actual Decodo credentials.

Faraday setup is even shorter because you embed the credentials in the proxy URL, and the JSON middleware parses the response:

require "faraday"
conn = Faraday.new("https://ip.decodo.com") do |f|
f.proxy = "http://#{ENV["DECODO_USER"]}:#{ENV["DECODO_PASS"]}@gate.decodo.com:7000"
f.response :json
end
data = conn.get.body
puts "Exit IP: #{data["proxy"]["ip"]} (#{data["country"]["name"]})"

You might have noticed none of these examples uses the ProxyRotator from the previous section. That’s because they don’t need to. You can easily handle rotation frequency or geolocation from your Decodo dashboard, and the gains are even more noticeable at scale.

Best practices and troubleshooting

Set timeouts on every request

Always set open_timeout and read_timeout. Proxies add latency, and sometimes a slow proxy can freeze the whole script.

Retry temporary failures with backoff

A timeout, dropped connection, or refused connection shouldn’t stop a whole scraping job. Retry a few times, wait longer between attempts, and switch proxies when the same endpoint keeps failing.

Read connection errors carefully

A connection refused error usually means the host, port, endpoint, or local network rule is wrong. For instance, a 407 Proxy Authentication Required response usually points to missing, wrong, or badly encoded credentials. 

Choose the proxy type based on the target

Residential proxies fit stricter websites and location-sensitive content. Datacenter proxies fit fast requests against simpler targets. ISP proxies are useful when a project needs stable sessions and stronger trust than standard datacenter IPs.

Use realistic request headers

A proxy changes the IP address, but it doesn’t make the full request look like browser traffic. Set a realistic User-Agent and include normal browser-like headers when scraping websites. For tougher defenses, see our guide on how to bypass CAPTCHAs.

Use browser automation when HTTP clients aren’t enough

Net::HTTP and Faraday are good for direct HTTP requests, but if your target requires dynamic rendering, browser-automation tools like Selenium may be more reliable.

Bottom line

In this article, you learned how to use proxies in Ruby with Net::HTTP and Faraday. You also saw how to configure authenticated proxies, rotate proxy endpoints, and integrate Decodo proxies into your Ruby applications.

If you don’t want to maintain proxy pools yourself, Decodo residential proxies give you a single gateway for rotating IPs, location targeting, and more reliable request routing.

Get residential proxies

Plug 115M+ from 195 locations residential proxies straight into your Ruby-powered pipelines to scale your projects in the most efficient way possible.

Share article:

About the author

Justinas Tamasevicius

Director of Engineering

Justinas Tamaševičius is Director of Engineering with over two decades of expertise in software development. What started as a self-taught passion during his school years has evolved into a distinguished career spanning backend engineering, system architecture, and infrastructure development.

Connect with Justinas via LinkedIn.

All information on Decodo Blog is provided on an as is basis and for informational purposes only. We make no representation and disclaim all liability with respect to your use of any information contained on Decodo Blog or any third-party websites that may belinked therein.

Frequently asked questions

How do I test if my proxy is working correctly in Ruby?

Send a request through the proxy to an IP-echo service and check the IP in the response. If it shows the proxy's address rather than your own, the proxy is working.

Are free proxies safe to use in Ruby applications?

Free proxies are risky for production applications. They are often slow, unstable, short-lived, and may expose request data. Paid proxies from a reputable provider are safer for scraping, testing, and automation.

Can I use SOCKS proxies with Ruby?

Yes, but SOCKS proxies aren’t as direct with Ruby’s standard Net::HTTP library. SOCKS support usually requires an additional gem, a compatible adapter, or a different client setup.

Authentication method showing users; Endpoint generator showing code & language selector on dark gradient

Web Scraping with Ruby: A Simple Step-by-Step Guide

Web scraping with Ruby might not be the first language that comes to mind for data extraction – Python usually steals the spotlight here. However, Ruby's elegant syntax and powerful gems make it surprisingly effective. This guide walks you through building Ruby scrapers from your first HTTP request to production-ready systems that handle JavaScript rendering, proxy rotation, and anti-bot measures. We'll cover essential tools like HTTParty and Nokogiri, show practical code examples, and teach you how to avoid blocks and scale safely.

Analytics dashboard showing a purple line chart with controls, centered on a dark dotted background.

What Is a Proxy Server? How It Works, Types, and Use Cases

TL;DR: A proxy server acts as an intermediary between your device and the internet, masking your IP address and routing your requests through alternative IPs. Businesses use proxies to bypass geo-restrictions, avoid blocks and CAPTCHAs, scrape web data at scale, verify ads, monitor prices, and maintain online anonymity. With the proxy market projected to reach USD 3.12 billion by 2032, understanding which proxy type fits your needs can make the difference between seamless data collection and constant roadblocks.

Browser window displaying cURL bubble, location pin marker, and </> code bubble on dark background

A Comprehensive Guide on Using Proxy with cURL in 2026

Whether you're a developer or an IT professional, data is an essential element of your everyday tasks. One of the most popular tools for data transfer is cURL (client for URL), which is embedded in almost every device that transfers data over different internet protocols.

However, when it comes to transferring data through a proxy, using cURL becomes even more critical. So, let's delve into the basics of cURL and proxies, discuss how it works, and get valuable tips on how to use cURL with proxy settings.

So, buckle up, pal, and get ready to learn how to use cURL with proxy and why it is essential in data transfer.

© 2018-2026 decodo.com (formerly smartproxy.com). All Rights Reserved