Generating Gzipped Assets for Nginx in Rails 4.2

Nginx has the ability to serve static .gz files instead of compressing assets on the fly, which is great for performance. Unfortunately, Rails recently dropped its support of this feature. Here’s how to restore it.


In older versions of Rails (specifically, before Sprockets 3 was introduced), you may have noticed that assets:precompile generated .gz versions of all the assets. A properly-configured Nginx web server can serve these gzipped versions instead of the uncompressed originals.

This is a great feature, because:

Unfortunately, this gzip feature was removed from the asset pipeline in Sprockets 3, and this generated a lot of discussion on GitHub. The most insightful comments can be found on the commit itself, and there has been some good feedback in a subsequent feature request.

As it turns out, the pre-generated gzip feature works great with Nginx, but causes subtle bugs with the Apache web server, which is also popular. Rather than enable by default a feature that breaks a significant portion of web servers, the Sprockets team decided not support the feature at all.

The good news is that the Rails team has stepped in and is planning to bring the feature back via an opt-in at some point. But what can we do in the meantime?

Implementing a workaround

If you use Nginx, chances are you want this feature back. Concerned developers have put forth various workarounds.

The solution I use is to extend the assets:precompile rake task to perform the gzipping. Here’s how I’ve implemented the rake task (which, by the way, is baked into any Rails app generated with my Rails template):

# Place this code in lib/tasks/assets.rake
namespace :assets do
  desc "Create .gz versions of assets"
  task :gzip => :environment do
    zip_types = /\.(?:css|html|js|otf|svg|txt|xml)$/

    public_assets = File.join(

    Dir["#{public_assets}/**/*"].each do |f|
      next unless f =~ zip_types

      mtime = File.mtime(f)
      gz_file = "#{f}.gz"
      next if File.exist?(gz_file) && File.mtime(gz_file) >= mtime, "wb") do |dest|
        gz =, Zlib::BEST_COMPRESSION)
        gz.mtime = mtime.to_i
        IO.copy_stream(open(f), gz)

      File.utime(mtime, mtime, gz_file)

  # Hook into existing assets:precompile task
  Rake::Task["assets:precompile"].enhance do

Note that:

Configuring Nginx

Finally, it is worth a reminder that all of this gzip work is effective only if Nginx is properly configured. Specifically, the gzip_static directive is that magic that instructs Nginx to look for the matching .gz files when an asset is requested.

Here’s the proper config, which you can also find mentioned in the old 4.1.x version of the asset pipeline documentation:

location ~ ^/(assets)/ {
  gzip_static on;