Error message
state=error: certificate verify failed (unable to get local issuer certificate)
Also printed as
- OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 peeraddr=127.0.0.1:8443 state=error: certificate verify failed (unable to get local issuer certificate)
The full message is "SSL_connect returned=1 errno=0 peeraddr=<ip>:<port> state=error: certificate verify failed (unable to get local issuer certificate)"; peeraddr was present with openssl gem 3.0.1 to 4.0.2 in our tests. Faraday wraps it as Faraday::SSLError with the same message; libraries that call Net::HTTP directly pass OpenSSL::SSL::SSLError through.
Ruby openssl (Net::HTTP, Faraday, HTTParty, RubyGems) — OpenSSL::SSL::SSLError from SSL_connect
What it means
Ruby's OpenSSL binding (used by Net::HTTP and therefore by most Ruby HTTP clients) could not find the issuer of the server's certificate chain among the CAs it loads. The text in parentheses is OpenSSL's verify reason; everything before it describes where in SSL_connect the failure happened. Usually the CA is private or behind a proxy, or this Ruby looks at a CA bundle that does not contain it.
Why it happens
A private CA or TLS-inspecting proxy
commonThe chain ends at a root that the OpenSSL store used by Ruby does not contain.
A Ruby built against its own OpenSSL (rbenv, asdf, rvm on macOS)
commonSuch Rubies use the CA bundle of the Homebrew or bundled OpenSSL, not the macOS Keychain; roots added to the Keychain by IT do not apply.
The server omits its intermediate
occasionalOpenSSL does not fetch missing intermediates, so a leaf-only server fails here although browsers accept it.
RubyInstaller on Windows
occasionalRuby on Windows ships its own CA bundle; company roots in the Windows store are not automatically part of it.
How to fix it
- 1.
Install the root where Ruby's OpenSSL looks
OpenSSL::X509::DEFAULT_CERT_FILE and DEFAULT_CERT_DIR show the paths; on Debian and Ubuntu, update-ca-certificates updates them.
bash # Ruby's OpenSSL reads the system bundle (see its default paths) ruby -ropenssl -e 'p OpenSSL::X509::DEFAULT_CERT_FILE, OpenSSL::X509::DEFAULT_CERT_DIR' sudo cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt && sudo update-ca-certificates - 2.
Set SSL_CERT_FILE for the process
OpenSSL honours SSL_CERT_FILE and SSL_CERT_DIR; point them at a bundle that contains the public roots and your CA.
bash # One process / environment: point OpenSSL at a bundle with the needed roots export SSL_CERT_FILE=/etc/ssl/private-ca/corp-ca-bundle.pem ruby app.rb - 3.
Give one client its own store
An OpenSSL::X509::Store with set_default_paths plus add_file keeps the public roots and adds the private CA, with full verification.
ruby # One client: default roots plus a private CA store = OpenSSL::X509::Store.new store.set_default_paths store.add_file("/etc/ssl/private-ca/corp-root-ca.pem") http = Net::HTTP.new("api.example.com", 443) http.use_ssl = true http.cert_store = store - 4.
Faraday and other wrappers: pass ca_file or cert_store
Most Ruby HTTP libraries accept ssl options that map to Net::HTTP's ca_file, ca_path or cert_store.
ruby conn = Faraday.new("https://api.example.com", ssl: { ca_file: "/etc/ssl/private-ca/corp-root-ca.pem" })
Related errors
state=error: certificate verify failed (self-signed certificate in certificate…Ruby openssl (Net::HTTP, Faraday, RubyGems)Could not verify the SSL certificate for https://gems.example.com/.Bundlercurl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local…Python ssl module (OpenSSL)