Fixing Ruby openssl errors
The other day I wanted to try out Rails 7’s import maps on my new M1 MacBook, but I was blocked by an error telling me that Ruby couldn’t make any SSL requests.
I’ll go through how I fixed this.
The command I tried that gave me an error was:
bin/importmap pin @picocss/pico
I got the following error about SSL:
/Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/protocol.rb:46:in `connect_nonblock': SSL_connect returned=1 errno=0 peeraddr=52.142.124.215:443 state=error: certificate verify failed (unable to get local issuer certificate) (OpenSSL::SSL::SSLError)
from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/protocol.rb:46:in `ssl_socket_connect'
from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/http.rb:1048:in `connect'
from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/http.rb:976:in `do_start'
from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/http.rb:965:in `start'
from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/http.rb:1530:in `request'
from (irb):25:in `<main>'
from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
from /Users/abuisman/.asdf/installs/ruby/3.1.0/bin/irb:25:in `load'
from /Users/abuisman/.asdf/installs/ruby/3.1.0/bin/irb:25:in `<main>'
I could reproduce this in a Ruby console with this code:
require 'uri'
require 'net/http'
require 'openssl'
url = URI("https://duckduckgo.com/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body
I’ve seen this error before and usually it happens when you haven’t compiled ruby properly with openssl. So I first reinstalled openssl@1.1 and openssl@3 and a few times again and again, but I couldn’t fix it.
I was ducking around for some info and I sort of picked up in my searching that this could mean that my CA certificates were outdated.
I’ll start with the fix:
brew uninstall openssl@1.1 --force --ignore-dependencies
brew install openssl@1.1
The --force --ignore-dependencies
is critical. I just ran uninstall
without it the first 10 times or so and I didn’t notice that nothing was really being removed because of dependencies. It could be that it was clear from the command output but I do not remember seeing anything relating to dependencies. --force --ignore-dependencies
was the thing that made a difference for me.
With the following .zshrc
configuration for compilation related things:
export OPTFLAGS="-Wno-error=implicit-function-declaration"
# readline
export LDFLAGS="-L/opt/homebrew/opt/readline/lib"
export CPPFLAGS="-I/opt/homebrew/opt/readline/include"
export PKG_CONFIG_PATH="/opt/homebrew/opt/readline/lib/pkgconfig"
# openssl config
export PATH="/opt/homebrew/opt/openssl@1.1:$PATH" # Might be overkill
export PATH="/opt/homebrew/opt/openssl@1.1/bin:$PATH"
export LIBRARY_PATH="/opt/homebrew/opt/openssl@1.1:$LIBRARY_PATH"
export RUBY_CONFIGURE_OPTS="--with-openssl-dir=/opt/homebrew/opt/openssl@1.1"
export LDFLAGS="-L/opt/homebrew/opt/openssl@1.1/lib:$LDFLAGS"
export CPPFLAGS="-I/opt/homebrew/opt/openssl@1.1/include:$CPPFLAGS"
export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@1.1/lib/pkgconfig:$PKG_CONFIG_PATH"
# libffi
export LDFLAGS="$LDFLAGS:-L/opt/homebrew/opt/libffi/lib"
export CPPFLAGS="$CPPFLAGS:-I/opt/homebrew/opt/libffi/include"
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/opt/homebrew/opt/libffi/lib/pkgconfig"
Then install ruby. For example with asdf
:
asdf install ruby 3.1.0
Oh and how do we know it works?
A colleague of mine pointed me to the bundler documentation and there they mention a nice command:
curl -Lks 'https://git.io/rg-ssl' | ruby
Bundler: How to troubleshoot RubyGems and Bundler TLS/SSL Issues
Before the fix the output looked like this:
Afterwards it looks like this:
This means we have a quick way to test the config without having to boot up a ruby console.
We have to could go deeper
In my recent migration from an i7 MacBook to this new M1 Pro I moved over my dotfiles with my .zshrc
config. I figured it was wrong at some point so I had a critical look. I must admit looked ridiculous:
export OPTFLAGS="-Wno-error=implicit-function-declaration"
# readline
export LDFLAGS="-L/opt/homebrew/opt/readline/lib"
export CPPFLAGS="-I/opt/homebrew/opt/readline/include"
export PKG_CONFIG_PATH="/opt/homebrew/opt/readline/lib/pkgconfig"
# openssl config
export PATH="/opt/homebrew/opt/openssl@1.1:$PATH"
export LIBRARY_PATH="/opt/homebrew/opt/openssl@1.1"
export RUBY_CONFIGURE_OPTS="--with-openssl-dir=/opt/homebrew/opt/openssl@1.1"
export LDFLAGS="-L/opt/homebrew/opt/openssl@1.1/lib"
export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@1.1/lib/pkgconfig"
# libffi
export LDFLAGS="-L/opt/homebrew/opt/libffi/lib"
export CPPFLAGS="$-I/opt/homebrew/opt/libffi/include"
export PKG_CONFIG_PATH="/opt/homebrew/opt/libffi/lib/pkgconfig"
So many of these variables are being overwritten in every section. It seems like this stuff accumulated while I was trying out things over time or as I was installing libraries. Who knows. Git shows many of these lines as new since moving to the new Mac.
What is the problem? For example LDFLAGS
gets overwritten like so:
export LDFLAGS="-L/opt/homebrew/opt/readline/lib"
export LDFLAGS="-L/opt/homebrew/opt/openssl@1.1/lib"
export LDFLAGS="-L/opt/homebrew/opt/libffi/lib"
So in the end the value is just -L/opt/homebrew/opt/libffi/lib
. You can see above that I corrected this by appending :$LDFLAGS"
each time. This means that it will add things to the LDFLAGS
variable instead of overwriting it. It turns out this cleanup didn’t do much, or at least reverting it didn’t break openssl again.
Interestingly, I had already built Ruby 2.7.4 already and it was showing the same issues. After fixing 3.1.0, 2.7.4 was also fixed. I believe this has something to do with new certificate files being downloaded as I mentioned earlier. My general idea is that reinstalling openssl properly also updated some certificate.
If you look here:
You can see that there is a certificate directory and a certificate file:
SSL_CERT_FILE: /opt/homebrew/etc/openssl@1.1/cert.pem
SSL_CERT_DIR: /opt/homebrew/etc/openssl@1.1/certs
When I look around in the directory it doesn’t contain anything and when I open the certificate file I see the following:
I have no clue what this certificate’s role is to be honest. I might find out and update here.
Conclusion
The fix was for me as described above. I ran into several github issues, stack overflows, etc. that allowed me to piece together these instructions. I hope my gathering of these solutions will be the real fix for you, or for me when I run into this again and forget.