Programming languages
Ruby OpenSSL::SSL::SSLError certificate verify failed: fixes
OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate)OpenSSL::SSL::SSLError ... certificate verify failed means Ruby's OpenSSL connected to the server but could not verify the certificate chain against the CA certificates it loads. Usually Ruby is looking at a CA file that does not exist or is outdated, or the remote server is not sending its intermediate certificate.
Quick fix
Pointing Ruby at a valid CA bundle fixes most cases.
Find out which OpenSSL and which CA paths your Ruby uses:
ruby -ropenssl -e 'puts OpenSSL::OPENSSL_LIBRARY_VERSION, OpenSSL::X509::DEFAULT_CERT_FILE, OpenSSL::X509::DEFAULT_CERT_DIR'
ls -l "$(ruby -ropenssl -e 'print OpenSSL::X509::DEFAULT_CERT_FILE')"
If the file is missing, point Ruby at a valid bundle:
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt # Debian/Ubuntu path
For gem and bundle problems, run bundle doctor ssl (recent Bundler) and gem update --system. If only one host fails, check its chain with the SSL checker.
Causes
1. Ruby's OpenSSL cannot find its CA file
Rubies built with rbenv/ruby-build, asdf or rvm link to a particular OpenSSL (often Homebrew's on macOS). If that OpenSSL has no certificate file, or it was removed during an upgrade, every verification fails.
- Confirm:
DEFAULT_CERT_FILEpoints to a path that does not exist or is empty. - Fix: install or update that OpenSSL's CA package, set
SSL_CERT_FILE, or reinstall the Ruby version so it links to a current OpenSSL.
2. Outdated CA certificates or RubyGems
Old systems and old RubyGems releases lack newer roots.
- Confirm: the failing host chains to a root that is newer than your OS or RubyGems version.
- Fix: update the OS
ca-certificatespackage and rungem update --systemandbundle update --bundler.
3. Wrong system clock
The RubyGems guide lists a wrong clock as one of the two main causes; a date in the past or future makes valid certificates look expired or not yet valid.
- Fix: correct the time and enable NTP synchronisation.
4. The remote server sends an incomplete chain
- Confirm:
openssl s_client -connect api.example.com:443 -servername api.example.com -showcertslists only one certificate. - Fix: the server must send its intermediates; see missing intermediate certificate and the chain builder.
5. A corporate proxy or internal CA
Roots that are not in the public bundle need to be added explicitly:
require "net/http"
require "openssl"
store = OpenSSL::X509::Store.new
store.set_default_paths
store.add_file("/etc/ssl/certs/company-root.pem")
http = Net::HTTP.new("intranet.example.com", 443)
http.use_ssl = true
http.cert_store = store
puts http.get("/").code
For a whole machine, add the root to the OS trust store instead. If the message reads self-signed certificate in certificate chain, see that page.
Fixes by environment
Every fix points Ruby's OpenSSL at a current CA file; on macOS that often means relinking Ruby to the current Homebrew OpenSSL.
macOS with Homebrew and rbenv/asdf: brew upgrade openssl@3 ca-certificates, then reinstall the affected Ruby with ruby-build so it links to the current OpenSSL. As a stopgap, export SSL_CERT_FILE="$(brew --prefix)/etc/ca-certificates/cert.pem".
Debian / Ubuntu:
sudo apt-get install --reinstall ca-certificates
sudo cp company-root.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates
RHEL family: place roots in /etc/pki/ca-trust/source/anchors/ and run update-ca-trust.
Windows (RubyInstaller): run the check command above to see where Ruby looks, and set SSL_CERT_FILE to a bundle that includes any corporate root.
Docker: install ca-certificates in the image and copy private roots in before bundle install.
Faraday / HTTParty: both accept a CA file or store (ssl: { ca_file: ... } in Faraday, ssl_ca_file: in HTTParty).
Don't do this
http.verify_mode = OpenSSL::SSL::VERIFY_NONE. It accepts any certificate from anyone; credentials and API responses can be intercepted.- Redefining
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE. This old snippet turns verification off for every library in the process. :ssl_verify_mode: 0in.gemrc, or switching gem sources tohttp://. Gems are code you execute; downloading them without authentication invites tampering.
FAQ
gem install or bundle install fails with certificate verify failed. What first?
Run bundle doctor ssl if your Bundler has it, then follow the RubyGems guide: update RubyGems and Bundler (gem update --system), check the system clock, and update the OS CA certificates. Most rubygems.org failures are fixed by one of these three.
Why does curl work while Ruby fails on the same machine?
Ruby uses the OpenSSL it was compiled against, which may be a different build with a different default CA path than the curl binary. Compare ruby -ropenssl -e 'puts OpenSSL::X509::DEFAULT_CERT_FILE' with the CAfile: line from curl -v, and set SSL_CERT_FILE if Ruby's file is missing.
It broke after a Homebrew or macOS upgrade. Why?
Rubies built by ruby-build or rvm link to a specific OpenSSL installation. If that OpenSSL or its certificate file moves or is removed, Ruby can no longer load roots. Reinstall the Ruby version (so it links to the current OpenSSL) or set SSL_CERT_FILE to a valid bundle.
How do I trust a corporate root in one HTTP client only?
Build a certificate store with the default roots plus your root and pass it to the client: store = OpenSSL::X509::Store.new; store.set_default_paths; store.add_file("/path/company-root.pem"), then http.cert_store = store for Net::HTTP or ssl: { cert_store: store } for Faraday.