# Patron wraps libcurl for Ruby and gives up the multi interface on purpose

> Patron is a Ruby HTTP client built as a C extension over libcurl, and it deliberately exposes a small API while keeping the parts that matter for a web client, connection reuse and GVL-friendly threading. It suits a Rails service making many outbound calls, and it carries a macOS SSL caveat that its own documentation puts at the top of the page.

**toland/patron** — Ruby HTTP client based on libcurl

- Repository: https://github.com/toland/patron
- Website: http://toland.github.com/patron/
- Stars: 547 · Forks: 75
- Language: C
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/toland-patron

## A thin Ruby API over libcurl rather than a full binding

The design decision is stated at the top of the documentation and it is the right thing to understand first. Patron does not try to expose the full read complexity of libcurl, and instead aims to provide a sane API while taking advantage of libcurl underneath. That means the Ruby surface is small and the C library underneath is not. The repository layout shows how that is built: a `lib/` directory for the Ruby code and an `ext/` directory for the extension, and the gemspec at the root packages both, with a Rakefile and a `spec/` directory of RSpec tests next to them. The primary language recorded for the repository is C, which is the honest description of where the work happens. A project like this has a natural lifetime tied to its C dependency and to the platform quirks of the libcurl build it links against, and understanding that is what tells you what to check before upgrading.

## One Session object carries the defaults, and there are three ways to set them

The entry point is a `Patron::Session` object, and the options you set on it become the defaults for every subsequent request. The documentation shows all three ways to do this, and the first is the simplest:

```ruby
sess = Patron::Session.new
sess.timeout = 10
sess.base_url = "http://myserver.com:9900"
sess.headers['User-Agent'] = 'myapp/1.0'
```

The second passes a hash to the constructor, with symbol keys for timeout, base_url and headers. The third uses a block, where the session is yielded and configured in place. The block form is worth preferring in application code because it makes the configuration scope obvious, while the setter form is convenient when the session is built in one place and configured later. Note that headers are set two different ways across the examples, once by indexing into the existing hash and once by assigning the whole hash, and that the base URL is a session-level default rather than something passed per request, which keeps the call sites short when a service talks to one host.

## Requests return a Response with status, body and libcurl transfer timings

Requests return a Response object, and the documented usage is a status check followed by the body:

```ruby
if resp.status < 400
  puts resp.body
end
```

GET, HEAD, PUT, POST and DELETE are all supported, with the body passed as a string and custom headers supplied per request as a third argument:

```ruby
sess.post("/foo/stuff", "some data", {"Content-Type" => "text/plain"})
```

The part worth knowing about is timings, which come straight from libcurl and are expressed in seconds since the request started. The documented output is a hash with namedelookup, connect, appconnect, pretransfer, starttransfer, redirect and total. For a service where an upstream call is a meaningful fraction of the request time, those numbers are the difference between guessing and knowing, and they cost nothing to collect because libcurl already computes them. The debug log is the other diagnostic lever: `sess.enable_debug "/tmp/patron.debug"` writes libcurl's own trace to a file, which is the first thing to reach for when a connection behaves differently behind a proxy than it does locally.

## A Session is not thread safe, so parallelism means a pool of them

The threading section is the most consequential part of the documentation, because it rules out an obvious design. `Patron::Session` objects are not thread safe on their own, and the reason is structural: each session holds a single `curl_state` pointer from initialization until garbage collection. There is also no support for the `curl_multi_*` family of functions, so one session cannot drive concurrent requests. The way to get parallelism is to use multiple sessions in different threads, and the documentation notes that the code interacting with libcurl does release the Ruby GVL, which is what makes that work at all. The recommended pattern is the connection_pool gem, with a pool of size five and a timeout of five seconds, used through a `with` block that yields a session. The same section explains the mechanics: the GVL is released when the libcurl request starts, briefly re-acquired to run the progress callback if one is configured, then released again until the response has been read in full. So a Ruby service can overlap network waits with work on other MRI threads.

## Connection reuse comes from keeping one session alive

Persistent connections are not a separate feature you switch on. Patron follows the libcurl guidelines on connection reuse, and the mechanism is the lifetime of the object: if you create the session once and use it for many requests, the same libcurl handle is used across them, and if the requests go to the same hostname, port and protocol the connection should be reused. That is the same reason the pool recommendation exists, since a pooled session is created once and handed out repeatedly. The documentation also says that sharing session objects between requests is what lets you benefit from persistent connections. The practical consequence for a Rails application is that a session created inside a request handler and thrown away at the end of it throws away the connection with it, so the reuse you are expecting quietly does not happen. This is the kind of library where the API is not the hard part and the object lifetime is.

## Protocol limiting is why the libcurl version floor matters

The requirements section is where the security content sits, and it is easy to skim past. A recent libcurl is required, and the documentation recommends at least 7.19.4 because that version supports limiting the protocols, which it calls very important for security, especially if you follow redirects. The reasoning is straightforward and worth repeating: an HTTP client that will follow a redirect to any scheme is a client that can be talked into fetching file URLs or other protocols it should never touch, and the ability to set an allow-list of protocols is the fix. The same section covers the build side. The libcurl development packages have to be installed on Debian or Ubuntu, and other Linux systems are described as probably similar. On macOS the bundled libcurl is said to be sufficient if you are not using the fork plus SSL combination. For Windows, the documentation is blunt: there is no established build instruction at the moment, unfortunately. The source is MIT licensed, and the copyright line in the readme goes back to 2008.

## macOS and forked servers with HTTPS is the failure mode to test for

The readme devotes a section to a specific and nasty problem, and it is the first thing to check if the deployment target is macOS. There is an open issue with macOS builds of curl that use Apple's SecureTransport, and such builds, which Patron links to, cause segfaults when performing HTTPS requests in forked subprocesses. The detection method is the Patron test suite itself, run in the install directory with bundle install and bundle exec rspec. The documentation notes that most default curl configurations on macOS, both the Apple-shipped version and the Homebrew one, are linked to SecureTransport and are likely to be affected. The blast radius is wider than it first appears, because the same issue may manifest in forking web server implementations such as Unicorn or Passenger and in forking job engines such as resque, so a service that never calls fork directly might still be doing it for you. The workaround is to build curl with OpenSSL via Homebrew and point the Patron build at that curl-config, either as a gem install flag or permanently through the Bundler build config for the patron gem.

## Installing the gem and running the suite locally

Installation is a gem install, and the documentation gives the command plainly:

```bash
    sudo gem install patron
```

A developer working on the library itself runs the test suite with bundler and rspec from the Patron install directory, which is the same command the macOS section asks you to use to detect the SSL problem:

```bash
    $ bundle install && bundle exec rspec
```

If you are on macOS and need to sidestep SecureTransport, the documented fix installs an OpenSSL-linked curl and rebuilds the gem against it:

```bash
    $ brew install curl-openssl && \
        gem install patron -- --with-curl-config=/usr/local/opt/curl-openssl/bin/curl-config
```

The same parameter can be persisted for every Bundler-driven install of the gem, so that a deploy pipeline does not silently fall back to the system curl:

```bash
    $ bundle config build.patron --with-curl-config=/usr/local/opt/curl-openssl/bin/curl-config
```

The repository also carries a Gemfile, a `.rspec` file, a `.travis.yml`, a `.yardopts` file for documentation generation and a CHANGELOG.md, and the GitHub Actions workflow badge at the top of the readme points at a ci.yml workflow. Release tags in the 0.x series run from v0.4.19 to v0.4.20, while the documentation describes a 1.0 and up line that requires MRI Ruby 2.3 or newer, with the 0.x versions supporting Ruby 1.9.3 and developed on the `v0.x` branch.

## Conclusion

Patron is a sound choice for a Ruby service that makes a high volume of outbound HTTP requests and wants libcurl's connection reuse and protocol handling without writing the C extension itself, since the session model keeps one handle alive per object and the GVL is released during the transfer. It is a poor fit if you need concurrent requests inside a single session, because the library states that it has no support for the `curl_multi_*` family and recommends a pool of sessions instead, and a poor fit on Windows, where the documentation says there is no established build instruction. Before adopting it, check the libcurl version on the target machine, since the documentation recommends at least 7.19.4 for the ability to limit protocols, and if the deployment runs on macOS with a forking server, test HTTPS in a forked process, because the readme says the Apple SecureTransport builds it links against cause segfaults there. The last push to the repository was on 2026-09-24.

## FAQ

### What is Patron and what does it use under the hood?

Patron is a Ruby HTTP client library built on libcurl, packaged as a C extension with Ruby code alongside it. The documentation is explicit that it does not try to expose the full read complexity of libcurl, but instead provides a small API while taking advantage of libcurl underneath.

### Is Patron::Session safe to share between Ruby threads?

No. Each session holds a single curl_state pointer from initialization to garbage collection, so sessions are not thread safe, and the library has no support for the curl_multi_* functions. The documentation recommends using a pool of sessions across threads, for example with the connection_pool gem, since the code that talks to libcurl releases the GVL.

### How do I install Patron?

The readme gives sudo gem install patron. You also need the libcurl development packages on Debian or Ubuntu, and the documentation recommends libcurl 7.19.4 or newer because that version supports limiting the protocols, which matters for security when following redirects.

### Does Patron work on macOS with a forking web server?

There is a known problem: macOS curl builds linked to Apple's SecureTransport, which Patron links to, can segfault on HTTPS requests in forked subprocesses. The workaround is to build curl with OpenSSL via Homebrew and point the gem install at that curl-config, and the readme asks you to run the test suite to check whether your system is affected.

## Sources

- [License: MIT](https://github.com/toland/patron/blob/master/LICENSE)
- [Project website](http://toland.github.com/patron/)
- [README](https://github.com/toland/patron/blob/master/README.md)
- [Releases](https://github.com/toland/patron/releases)
- [toland/patron on GitHub](https://github.com/toland/patron)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/toland-patron
