If you are writing a parser, integrating an external API, or automating data collection in Ruby — sooner or later you will encounter IP blocks. Wildberries, Ozon, Instagram, and dozens of other services limit requests from a single address after just a few hundred calls. The solution is simple — proxies. In this article, we will explore how to connect proxies to a Ruby project using the standard Net::HTTP and the popular HTTP client Faraday, set up IP rotation, and properly handle errors — both in a Rails application and in simple scripts.
Why Ruby Developers Need Proxies: Main Scenarios
Ruby is actively used for tasks where proxies are simply indispensable. Before diving into the code, it's worth understanding in which situations they are needed — this will help you choose the right type and architecture of the solution.
Parsing and Data Collection. This is the most common scenario. You are writing a Ruby script that scrapes pages from Wildberries, Ozon, or Avito, collecting prices and product characteristics. Without proxies, such a script will be blocked by IP in just a few minutes. Websites see hundreds of requests from a single address and automatically enable protection — CAPTCHA, temporary bans, or complete denial of service.
Integration with Geo-dependent APIs. Some external services return different content depending on the request's country. If your Rails application accesses such an API, proxies allow you to simulate requests from the desired region. For example, checking search engine results for different countries or obtaining prices relevant to a specific market.
Testing and QA. Developers use proxies to test application behavior when requests come from different IP addresses and countries. This is especially relevant for services with geo-blocking or rate-limiting by IP.
Marketing Automation. Ruby scripts are often used to monitor advertising campaigns, check rankings in search results, or track competitor activity. Here, proxies are needed to distribute the load and bypass restrictions.
Working with Multiple Accounts. If your script manages multiple accounts on one platform, each account must operate through a separate IP — otherwise, the platform can easily detect the connection and block everything at once.
Which Type of Proxy to Choose for Ruby Tasks
Not all proxies are equally suitable for different tasks. The choice of type directly affects the success of your script's operation. Let's discuss the main options relevant to Ruby development.
| Proxy Type | Best Tasks in Ruby | Speed | Bypassing Protections |
|---|---|---|---|
| Datacenter Proxies | Parsing without strict protection, API requests, testing | High | Medium |
| Residential Proxies | Parsing protected sites, working with social media, marketplaces | Medium | High |
| Mobile Proxies | Facebook, Instagram, TikTok API, account management | Medium | Maximum |
Datacenter Proxies are the fastest and cheapest option. They are suitable for scraping sites without aggressive protection, mass API requests, and load testing. However, large platforms (Facebook, Google, Cloudflare) can easily recognize them by the ASN of the datacenter.
Residential Proxies use IPs of real home users. For Ruby scripts that scrape Wildberries, Ozon, or work with protected sites — this is the optimal choice. Websites see a regular user, not a server.
Mobile Proxies operate through IPs of mobile operators (4G/5G). These are the most trusted addresses from the platform's perspective — a single mobile IP can be used by hundreds of real users simultaneously, so blocks on them are extremely rare. If your Ruby script works with social networks or advertising platforms — mobile proxies will minimize the risk of blocks.
Proxies in Net::HTTP: Basic Setup
Net::HTTP is the standard Ruby library for HTTP requests, included in the standard distribution of the language. It supports proxies "out of the box" through a special method Net::HTTP::Proxy.
The simplest way is to create a proxy class and use it instead of the regular Net::HTTP:
require 'net/http'
require 'uri'
# Proxy data
proxy_host = 'proxy.example.com'
proxy_port = 8080
# Create a proxy class
proxy_class = Net::HTTP::Proxy(proxy_host, proxy_port)
# Execute a request through the proxy
uri = URI('https://httpbin.org/ip')
proxy_class.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
request = Net::HTTP::Get.new(uri)
response = http.request(request)
puts response.body
end
The Net::HTTP::Proxy method takes four parameters: proxy host, port, username, and password. The first two are mandatory, the last two are optional for proxies without authentication.
An alternative way is to pass proxy parameters directly to the Net::HTTP.new method:
require 'net/http'
uri = URI('https://httpbin.org/ip')
http = Net::HTTP.new(
uri.host,
uri.port,
'proxy.example.com', # proxy_addr
8080, # proxy_port
nil, # proxy_user (nil if no authentication)
nil # proxy_pass
)
http.use_ssl = true
response = http.get(uri.path)
puts response.body
This approach is convenient when you need to reuse the same HTTP object for multiple requests to the same host. Note that proxy parameters are passed as the third and fourth arguments, not through a separate method.
Authentication and HTTPS via Net::HTTP
Most commercial proxies require authentication — a username and password. Additionally, when working with HTTPS sites, extra SSL configuration is needed. Let's discuss both cases.
Proxies with Username and Password:
require 'net/http'
require 'uri'
proxy_host = 'proxy.example.com'
proxy_port = 8080
proxy_user = 'your_username'
proxy_pass = 'your_password'
# Create a proxy class with authentication
proxy_class = Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass)
uri = URI('https://httpbin.org/ip')
proxy_class.start(uri.host, uri.port, use_ssl: true) do |http|
# Disable SSL verification (for testing only!)
# http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Get.new(uri)
request['User-Agent'] = 'Mozilla/5.0 (compatible; MyBot/1.0)'
response = http.request(request)
puts "IP: #{response.body}"
puts "Status: #{response.code}"
end
An important point: when using HTTPS proxies (tunneling via CONNECT), Net::HTTP automatically establishes an SSL connection with the target server through the proxy tunnel. This works correctly for most proxy servers.
Configuration via Environment Variables. A good practice is to avoid hardcoding proxy data in the code and instead read them from environment variables:
require 'net/http'
require 'uri'
# Read proxy settings from ENV
proxy_uri = URI(ENV.fetch('HTTP_PROXY', 'http://proxy.example.com:8080'))
proxy_class = Net::HTTP::Proxy(
proxy_uri.host,
proxy_uri.port,
proxy_uri.user,
proxy_uri.password
)
target_uri = URI('https://httpbin.org/ip')
proxy_class.start(target_uri.host, target_uri.port, use_ssl: true) do |http|
response = http.get(target_uri.path)
puts response.body
end
The environment variable can be set in the format HTTP_PROXY=http://user:[email protected]:8080. This approach allows you to change the proxy without modifying the code — simply update the variable in the .env file or in the server settings.
Connecting Proxies via Faraday
Faraday is one of the most popular HTTP clients in the Ruby ecosystem. It is used in many Rails applications due to its convenient middleware approach and support for various adapters (Net::HTTP, HTTParty, Typhoeus, and others). Setting up proxies in Faraday differs slightly depending on the adapter used.
Basic Proxy Setup in Faraday:
require 'faraday'
# Create a Faraday connection with a proxy
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.proxy = {
uri: 'http://proxy.example.com:8080',
user: 'your_username',
password: 'your_password'
}
faraday.headers['User-Agent'] = 'Mozilla/5.0 (compatible; MyBot/1.0)'
faraday.adapter Faraday.default_adapter
end
response = conn.get('/ip')
puts response.body
puts "Status: #{response.status}"
The faraday.proxy parameter accepts a hash with keys uri, user, and password. You can also pass a URI string directly if the proxy does not require authentication.
Faraday with Net::HTTP Adapter and Advanced Settings:
require 'faraday'
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.proxy = 'http://user:[email protected]:8080'
# Timeout settings
faraday.options.timeout = 30
faraday.options.open_timeout = 10
# Middleware for logging (useful for debugging)
faraday.response :logger
# Retry on connection errors
faraday.request :retry, max: 3, interval: 1
# Adapter
faraday.adapter :net_http
end
begin
response = conn.get('/ip')
puts response.body
rescue Faraday::ConnectionFailed => e
puts "Connection error: #{e.message}"
rescue Faraday::TimeoutError => e
puts "Timeout: #{e.message}"
end
The :retry middleware is particularly useful when working with proxies — if one address is temporarily unavailable, Faraday will automatically retry the request. This is critical for reliable script operation in production.
Faraday with Typhoeus Adapter (for parallel requests):
# Gemfile: gem 'typhoeus'
require 'faraday'
require 'typhoeus/adapters/faraday'
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.proxy = 'http://user:[email protected]:8080'
faraday.adapter :typhoeus
end
# Parallel requests through the proxy
responses = []
conn.in_parallel do
responses << conn.get('/ip')
responses << conn.get('/headers')
responses << conn.get('/user-agent')
end
responses.each { |r| puts r.body }
Proxy Rotation: Automatic IP Change
One proxy address, even a residential one, will eventually fall under restrictions during intensive scraping. The solution is rotation: automatic IP change after each request or at certain intervals. Let's implement a simple rotator for Ruby scripts.
Simple Rotator Based on an Array of Proxies:
require 'net/http'
require 'uri'
class ProxyRotator
def initialize(proxies)
@proxies = proxies
@index = 0
@mutex = Mutex.new
end
def next_proxy
@mutex.synchronize do
proxy = @proxies[@index % @proxies.length]
@index += 1
proxy
end
end
def random_proxy
@proxies.sample
end
end
# List of proxies in the format [host, port, user, pass]
proxies = [
['proxy1.example.com', 8080, 'user1', 'pass1'],
['proxy2.example.com', 8080, 'user2', 'pass2'],
['proxy3.example.com', 8080, 'user3', 'pass3'],
]
rotator = ProxyRotator.new(proxies)
# Scraping a list of URLs through different proxies
urls = [
'https://httpbin.org/ip',
'https://httpbin.org/headers',
'https://httpbin.org/user-agent'
]
urls.each do |url|
proxy = rotator.next_proxy
proxy_class = Net::HTTP::Proxy(proxy[0], proxy[1], proxy[2], proxy[3])
uri = URI(url)
begin
proxy_class.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.read_timeout = 15
response = http.get(uri.path)
puts "#{url} -> Status: #{response.code}, Proxy: #{proxy[0]}"
end
rescue => e
puts "Error with proxy #{proxy[0]}: #{e.message}"
# Automatically move to the next proxy
end
sleep(rand(0.5..2.0)) # Random delay between requests
end
A random delay between requests (sleep(rand(0.5..2.0))) is an important element. The pattern of requests with fixed intervals is easily detected by anti-bot systems. Random delays simulate real user behavior.
Rotation via Faraday with Middleware:
require 'faraday'
class ProxyMiddleware < Faraday::Middleware
PROXIES = [
'http://user1:[email protected]:8080',
'http://user2:[email protected]:8080',
'http://user3:[email protected]:8080',
].freeze
def call(env)
# Choose a random proxy for each request
proxy_uri = URI(PROXIES.sample)
env[:request][:proxy] = {
uri: proxy_uri,
user: proxy_uri.user,
password: proxy_uri.password
}
@app.call(env)
end
end
# Register middleware
Faraday::Middleware.register_middleware proxy_rotator: ProxyMiddleware
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.use :proxy_rotator
faraday.adapter :net_http
end
5.times do
response = conn.get('/ip')
puts response.body
end
Integrating Proxies into a Rails Application
In a Rails application, proxies are most often needed for external HTTP requests: API integrations, background parsing tasks, or webhooks. Let's consider several practical patterns.
Pattern 1: Service Object with Faraday. Create a basic service class that makes all external HTTP requests through a proxy:
# app/services/http_client.rb
class HttpClient
def self.connection(base_url)
Faraday.new(url: base_url) do |faraday|
# Proxy from Rails environment variables
if Rails.env.production? && ENV['PROXY_URL'].present?
faraday.proxy = ENV['PROXY_URL']
end
faraday.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
faraday.options.timeout = 30
faraday.options.open_timeout = 10
faraday.request :retry, max: 3, interval: 2,
exceptions: [Faraday::ConnectionFailed, Faraday::TimeoutError]
faraday.response :raise_error
faraday.adapter :net_http
end
end
end
# Usage in a controller or service:
# client = HttpClient.connection('https://api.example.com')
# response = client.get('/products', { category: 'electronics' })
Pattern 2: Configuration via Rails Credentials. For storing proxy data, use encrypted Rails credentials:
# config/credentials.yml.enc (edited via rails credentials:edit)
# proxy:
# host: proxy.example.com
# port: 8080
# username: your_user
# password: your_pass
# app/services/proxy_service.rb
class ProxyService
def self.faraday_proxy_config
creds = Rails.application.credentials.proxy
return nil unless creds
{
uri: "http://#{creds[:host]}:#{creds[:port]}",
user: creds[:username],
password: creds[:password]
}
end
def self.net_http_proxy
creds = Rails.application.credentials.proxy
return Net::HTTP::Proxy(nil, nil) unless creds
Net::HTTP::Proxy(
creds[:host],
creds[:port],
creds[:username],
creds[:password]
)
end
end
Pattern 3: Background Tasks with Sidekiq. If parsing or API requests are performed in background workers, the proxy setup remains the same, but it is important to consider concurrency:
# app/workers/price_parser_worker.rb
class PriceParserWorker
include Sidekiq::Worker
sidekiq_options retry: 3, queue: :parsers
def perform(product_url)
# Create a new connection for each worker
proxy = ProxyService.net_http_proxy
uri = URI(product_url)
proxy.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.read_timeout = 20
request = Net::HTTP::Get.new(uri)
request['User-Agent'] = random_user_agent
response = http.request(request)
if response.code == '200'
parse_and_save(response.body, product_url)
else
Rails.logger.warn "Unexpected status #{response.code} for #{product_url}"
end
end
rescue Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error "Timeout for #{product_url}: #{e.message}"
raise # Sidekiq will retry the task
end
private
def random_user_agent
agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
]
agents.sample
end
def parse_and_save(html, url)
# Parsing logic...
end
end
Error Handling and Timeouts
Working through proxies adds an additional layer that can generate specific errors. Proper exception handling is the difference between a script that crashes after 5 minutes and one that runs for hours without intervention.
Main Types of Errors When Working with Proxies in Ruby:
| Exception | Cause | What to Do |
|---|---|---|
Net::OpenTimeout |
Proxy is not responding | Change proxy, retry |
Net::ReadTimeout |
Proxy is slow or the site is not responding | Increase timeout or change proxy |
Errno::ECONNREFUSED |
Proxy server refused the connection | Check proxy data |
OpenSSL::SSL::SSLError |
SSL issue through the proxy | Check SSL settings |
| HTTP 407 | Invalid proxy username/password | Check credentials |
| HTTP 403/429 | IP blocked by the site | Change proxy, add a pause |
Comprehensive Error Handling with Automatic Proxy Change:
require 'net/http'
require 'uri'
class RobustHttpClient
MAX_RETRIES = 3
RETRIABLE_ERRORS = [
Net::OpenTimeout,
Net::ReadTimeout,
Errno::ECONNREFUSED,
Errno::ECONNRESET,
OpenSSL::SSL::SSLError
].freeze
def initialize(proxies)
@proxies = proxies.dup
@failed_proxies = []
end
def get(url, retries: MAX_RETRIES)
uri = URI(url)
attempt = 0
begin
attempt += 1
proxy = current_proxy
proxy_class = Net::HTTP::Proxy(*proxy)
proxy_class.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.open_timeout = 10
http.read_timeout = 20
request = Net::HTTP::Get.new(uri)
request['User-Agent'] = 'Mozilla/5.0 (compatible; RubyBot/1.0)'
response = http.request(request)
case response.code.to_i
when 200..299
response
when 407
raise "Proxy authentication failed for #{proxy[0]}"
when 403, 429
rotate_proxy!
raise "IP blocked (#{response.code}), rotating proxy"
else
raise "Unexpected HTTP status: #{response.code}"
end
end
rescue *RETRIABLE_ERRORS => e
Rails.logger.warn "Connection error (attempt #{attempt}/#{retries}): #{e.message}"
rotate_proxy!
retry if attempt < retries
raise "Failed after #{retries} attempts: #{e.message}"
rescue RuntimeError => e
Rails.logger.warn "HTTP error (attempt #{attempt}/#{retries}): #{e.message}"
retry if attempt < retries
raise
end
end
private
def current_proxy
@proxies.first || raise("No available proxies!")
end
def rotate_proxy!
failed = @proxies.shift
@failed_proxies << failed
Rails.logger.info "Rotated proxy. Remaining: #{@proxies.length}"
end
end
SOCKS5 Proxies in Ruby: Setup via Socksify
The standard Net::HTTP does not natively support SOCKS5 — only HTTP/HTTPS proxies. To work with SOCKS5, an additional library is needed. The most popular option is the socksify gem.
Why might SOCKS5 be preferable to HTTP proxies? The SOCKS5 protocol operates at a lower level — it proxies any TCP traffic, not just HTTP. Additionally, SOCKS5 supports authentication and transmits less metadata about the request, making it less detectable by bot detection systems.
# Gemfile
# gem 'socksify'
require 'socksify'
require 'socksify/http'
require 'net/http'
require 'uri'
# Global SOCKS5 proxy settings
TCPSocket::socks_server = 'proxy.example.com'
TCPSocket::socks_port = 1080
# Optionally: authentication (SOCKS5 username/password)
# TCPSocket::socks_username = 'your_user'
# TCPSocket::socks_password = 'your_pass'
# Now all Net::HTTP requests go through SOCKS5
uri = URI('https://httpbin.org/ip')
response = Net::HTTP.get_response(uri)
puts response.body
Note: when using socksify, the configuration applies globally to all TCP connections in the process. If you need more flexible settings — use Net::HTTP.SOCKSProxy from the same gem:
require 'socksify/http'
# Create a SOCKS5 proxy class (similar to Net::HTTP::Proxy for HTTP)
socks_proxy = Net::HTTP.SOCKSProxy('proxy.example.com', 1080)
uri = URI('https://httpbin.org/ip')
socks_proxy.start(uri.host, uri.port, use_ssl: true) do |http|
response = http.get(uri.path)
puts response.body
end
For Faraday with SOCKS5, you will need an adapter that supports this protocol. A good option is faraday-net_http in conjunction with socksify, or using the curl-based adapter faraday-typhoeus, which natively supports SOCKS5 through libcurl.
💡 Protocol Selection Tip
For most scraping and API tasks, HTTP/HTTPS proxies are sufficient — they are easier to set up and supported natively. SOCKS5 should be used when you need to proxy not just HTTP traffic, or when HTTP proxies are easily detected by the target service. Mobile and residential proxies are available in both protocols.
Conclusion and Recommendations
Setting up proxies in Ruby does not require complex libraries — the standard Net::HTTP supports HTTP proxies "out of the box," and Faraday makes integration even more convenient thanks to its middleware architecture. Key takeaways from this guide:
- For Net::HTTP, use
Net::HTTP::Proxy— this is a built-in and reliable method. - For Faraday, the
faraday.proxyparameter accepts a URI string or a hash with credentials. - Store proxy data in environment variables or Rails credentials — do not hardcode them in the code.
- Implement proxy rotation and error handling for reliable operation in production.
- Add random delays and rotate User-Agent to simulate human behavior.
- For SOCKS5, use the
socksifygem.
The choice of proxy type depends on the task. If you are writing a parser for Wildberries or Ozon — residential proxies will provide the best balance between speed and bypassing protections: real home user IPs rarely end up in blocklists. For high-load tasks with a large number of requests to less protected sources, datacenter proxies will be faster and cheaper per request. If your Ruby script works with social networks or advertising APIs — consider mobile proxies: their IP addresses have the highest level of trust on most platforms.