Configuring wkhtmltopdf on MacOS and Linux

:: programming, ruby, rails, macos, linux

I’ve been using wkhtmltopdf in Rails projects for years. After upgrading to Rails 6 and Ruby 2.6, PDF creation started failing for me. This post documents what I did to get it to work again.

Background

As of 12/4/2020, my deployment server is running Ubuntu 16.04.6 LTS, my development machine is running MacOS 10.15.7, and both are running ruby 2.6.6p146 & Rails 6.0.3.3. The relevant gems are pdfkit 0.8.4.3.2 & wkhtmltopdf-binary 0.12.6.5.

The original error message simply stated wkhtmltopdf failed with an exit status of 1.

Command failed (exitstatus=1): /.../versions/2.6.6/bin/wkhtmltopdf --quiet --page-size Letter --margin-top 4mm --margin-right 4mm --margin-bottom 27mm --margin-left 4mm --encoding UTF-8 --cookie <cookie name> <cookie data> --footer-html pdf_footer.html --footer-spacing 2 "http://localhost:<port>/.../report/1328" -

Although I’m using the pdfkit gem, all of the issues are with wkhtmltopdf, so they may be relevant to you even if you interface with wkhtmltopdf using some other means.

Issue 1: Unable to access local file containing footer html

I discovered the first issue was the inability to read the pdf_footer.html file - see line 2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
options.reverse_merge!({ :cookie         => { '<cookie name>' => session_cookie },
                         :footer_html    => 'pdf_footer.html',
                         :footer_spacing => 2,
                         :margin_bottom  => '27mm',
                         :margin_left    => '4mm',
                         :margin_right   => '4mm',
                         :margin_top     => '4mm'})

pdfkit = PDFKit.new(url, options)
pdf    = pdfkit.to_pdf

The solution to this is to add the enable_local_file_access option for wkhtmltopdf (see line 2):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
options.reverse_merge!({ :cookie                   => { '<cookie name>' => session_cookie },
                         :enable_local_file_access => true,
                         :footer_html              => 'pdf_footer.html',
                         :footer_spacing           => 2,
                         :margin_bottom            => '27mm',
                         :margin_left              => '4mm',
                         :margin_right             => '4mm',
                         :margin_top               => '4mm'})

pdfkit = PDFKit.new(url, options)
pdf    = pdfkit.to_pdf

Issue 2: File permissions on linux

The next problem was related to file permissions on linux:

$ wkhtmltopdf --help
Traceback (most recent call last):
        4: from /usr/local/bin/wkhtmltopdf:23:in `<main>'
        3: from /usr/local/bin/wkhtmltopdf:23:in `load'
        2: from /usr/local/lib/ruby/gems/2.6.0/gems/wkhtmltopdf-binary-0.12.6.5/
           bin/wkhtmltopdf:55:in `<top (required)>'
        1: from /usr/local/lib/ruby/gems/2.6.0/gems/wkhtmltopdf-binary-0.12.6.5/
           bin/wkhtmltopdf:55:in `open' /usr/local/lib/ruby/gems/2.6.0/gems/
           wkhtmltopdf-binary-0.12.6.5/bin/wkhtmltopdf:55:in `initialize':
Permission denied @ rb_sysopen - /usr/local/lib/ruby/gems/2.6.0/gems/
  wkhtmltopdf-binary-0.12.6.5/bin/wkhtmltopdf_ubuntu_16.04_amd64 (Errno::EACCES)

I’m sure there’s a better way to handle this, I read accounts of needing to provide write access to the gem directory where wkhtmltopdf lived, so:

cd /usr/local/lib/ruby/gems/2.6.0/gems/wkhtmltopdf-binary-0.12.6.5/bin

sudo chmod 777 .

Issue 3: Failing to authenticate properly

After fixing the above two issues, I was able to create PDF files of my report web page; however, the resulting PDF was of the login page, so apparently authentication was failing. Previously to upgrading ruby & Rails, it was sufficient to pass a cookie option to wkhtmltopdf (see line 1):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
options.reverse_merge!({ :cookie                   => { '_<app-name>_session' => session_cookie },
                         :enable_local_file_access => true,
                         :footer_html              => 'pdf_footer.html',
                         :footer_spacing           => 2,
                         :margin_bottom            => '27mm',
                         :margin_left              => '4mm',
                         :margin_right             => '4mm',
                         :margin_top               => '4mm'})

pdfkit = PDFKit.new(url, options)
pdf    = pdfkit.to_pdf

The session_cookie value was obtained via cookies['_<app-name>_session'] in the controller. When I looked at the value of cookies['_<app-name>_session'], I noticed it was different than the value stored in my browser’s cookie. However, that appears to be a red herring as there are reasons why Rails may return different values of encrypted cookies.

The root of the problem seems to be that the value of the cookie now needs to be escaped via: CGI.escape(cookies['_<app-name>_session']). After doing this, wkhtmltopdf was able to successfully authenticate.