nginx + Rails 3.1 + Cloudfront assets subdirectories 404 missing - ruby-on-rails

I'm using Rails 3.1 asset pipeline which is served using a custom origin Cloudfront CDN.
This is what I have written in my nginx.conf to serve the assets in gzip and for caching:
location ^~ /assets/ {
allow all;
gzip_http_version 1.0;
gzip_static on;
expires 365d;
add_header Last-Modified "";
add_header ETAg "";
add_header Cache-Control public;
}
The problem is that subdirectories e.g. /background/ in my images asset folder have their items missing with 404s.
When I disable the nginx location config above the problem goes away. How do I configure nginx properly to serve the assets in the subdirectories?
Thanks

This worked for me:
location ~ ^/(assets)/  {
root /opt/appname/public; # or whatever the path is to your app's public folder
gzip_http_version 1.0;
gzip_static on;
access_log off;
expires 1y;
add_header Cache-Control public;
add_header Last-Modified "";
add_header ETag "";
break;
}

Related

Nginx how to serve digest favicon file when request is for non-digest favicon.ico

I'm using Nginx in front of a rails app. Nginx is serving static assets from public with the following location rule:
location ~ ^/assets/ {
gzip_static on;
gzip off;
expires 1y;
add_header Cache-Control public;
add_header Last-Modified "";
add_header ETag "";
break;
}
This is working fine for the standard Rails digest assets but every now and then I get a request for /favicon.ico directly which will fail as it is a non-digest asset.
What I'd like to do is use a Nginx location directive to map/alias/rewrite /favicon.ico to /assets/favicons/favicon-somereallylongdigest.ico.
As rails will keep up to the last 3 assets there could be three files which match, I'm not worried about that and are happy to match any of the files found.
I'm not strong on Nginx config so any help is appreciated.
You could use a permanent redirect:
location = /favicon.ico {
return 301 $scheme://$host/assets/favicons/favicon-somereallylongdigest.ico;
}

Deploying multiple versions of a Rails API using nginx

I use nginx, with passenger, to serve a Rails API. In preparation for our 1.0 launch, I would like to version this API using the method described in this answer.
I have a version number that is pulled from a specification, along with capistrano recipes set up to create the following directory structure:
/var/www/foamfactory.io/dev.api.foamfactory.io
- v0
- current (points to v0.0)
- v0.0
- current (points to v0.0.15)
- v0.0.15 (this is the capistrano directory)
- current (points to latest release under releases/)
- releases
- 20210308162204 (rails directory)
Now, my nginx setup was as follows, in order to rewrite, for example, dev.api.foamfactory.io/version to dev.api.foamfactory.io/current/current/current/current/current/public (I'm working on getting this basic example working before I struggle with, e.g dev.api.foamfactory.io/v0/v0.0/version.
server {
rewrite_log on;
server_name dev.api.foamfactory.io;
access_log /var/log/nginx/dev.api.foamfactory.io-access.log;
error_log /var/log/nginx/dev.api.foamfactory.io-error.log notice;
passenger_enabled on;
passenger_ruby /home/www-data/.rvm/rubies/default/bin/ruby;
passenger_sticky_sessions on;
passenger_app_env development;
passenger_env_var SECRET_KEY_BASE redacted;
location / {
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
}
rewrite ^/(.*)$ /current/current/current/current/public/$1 last;
rewrite ^/v([0-9]+)/(.*)$ /v$1/current/current/current/public/$2 last;
rewrite ^/v([0-9]+).([0-9]+)/(.*)$ /v$1/v$1.$2/current/current/public/$3 last;
rewrite ^/v([0-9]+).([0-9]+).([0-9]+)/(.*)$ /v$1/v$1.$2/v$1.$2.$3/current/public/$4 last;
}
I modified this first rewrite statement to be a location/ statement:
server {
rewrite_log on;
server_name dev.api.foamfactory.io;
access_log /var/log/nginx/dev.api.foamfactory.io-access.log;
error_log /var/log/nginx/dev.api.foamfactory.io-error.log notice;
passenger_enabled on;
passenger_ruby /home/www-data/.rvm/rubies/default/bin/ruby;
passenger_sticky_sessions on;
passenger_app_env development;
passenger_env_var SECRET_KEY_BASE redacted;
location / {
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
}
location ~ ^/(.*)$ {
rewrite ^/(.*)$ /current/current/current/current/public/$1 break;
root /var/www/foamfactory.io/dev.api.foamfactory.io/current/current/current/current/public;
}
}
I can now access the Rails app, BUT, when I try to access an endpoint (e.g. /version, as in dev.api.foamfactory.io/version, Rails reports the following error:
ActionController::RoutingError: No route matches [GET] \"/current/current/current/current/public/version\"
My suspicion is that it has something to do with the relative_url_root configuration variable inside of the Rails configuration, so inside of environments/development.rb, I modified the configuration to include:
config.relative_url_root = '/current/current/current/current/public'
However, this did not alleviate the error.
I'm wondering if someone might be able to help me with what I need to do to get this working. I feel like I'm not even sure if it's an nginx configuration problem or a Rails configuration problem at this point.

nginx location block comprehension and using passenger with named location blocks

I have a couple of questions regarding my nginx configuration as it pertains to serving webp files as well as using named locations with try_files.
Current config:
server {
listen 80;
server_name assets.manager manager;
passenger_app_env production;
passenger_ruby /home/web-server/.rvm/gems/ruby-2.2.1#manager/wrappers/ruby;
passenger_enabled on;
error_log /home/web-server/web-applications/manager/current/log/nginx-error.log;
root /home/web-server/web-applications/manager/current/public;
satisfy any;
allow 127.0.0.1;
allow 192.168.0.0/24;
deny all;
location ~ ^/assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
add_header Last-Modified "";
add_header ETag "";
}
location ~* .+\.(?:png|jpe?g|gif)$ {
if ($webp_suffix != "") {
add_header Vary Accept;
}
try_files $uri$webp_suffix $uri =404;
}
}
As it stands, nginx is not serving the webp files. If I were to place add_header X-Webp-Uri "$uri$webp_suffix"; inside the first location block, the header is added. If I were to put that in the second png/jpeg matching location block the header doesn't get set. It was my understanding that regular expression location blocks are sequential (i.e., it doesn't stop matching at the 1st match).
1) I want to serve webp images if present (my attempt at this is the 2nd location block). Would having a nested location block help in this circumstance? I want to set gzip_static, expires, etc. for ALL files in /assets/, but I only want to serve webp version if they exist for certain file extensions within /assets/.
2) On another topic, I want to serve static .html files if present. To do this (after looking up tutorials on the web) I need a combination of try_files and a named location block that points to an upstream application (Rails). However I can't seem to find out how to declare the upstream block if I'm using Passenger (installed using passenger-install-nginx-module). The only configurations I can find for a Passenger/Nginx setup is to use passenger_enabled on;
EDIT: I found a sample configuration; here's an example (this ignores problem #1):
server {
listen 80;
server_name assets.staging.pos staging.pos;
passenger_app_env staging;
passenger_ruby /home/vagrant/.rvm/gems/ruby-2.2.1#pos/wrappers/ruby;
passenger_enabled on;
error_log /home/vagrant/rails/staging.pos/log/nginx-error.log;
root /home/vagrant/rails/staging.pos/public;
try_files $uri /cache/$uri /cache/$uri.html #app;
location #app {
proxy_set_header X-Forwarded-Proto http;
}
location ~* ^/images/.+\.(png|jpe?g)$ {
if ($webp_suffix != "") {
add_header Vary Accept;
}
try_files $uri$webp_suffix $uri =404;
}
}
EDIT 2: I've discovered that nginx completely wipes out any instruction outside of the if block!
This will result in only the Vary Accept header being set:
server {
location ~* ^/images/.+\.(png|jpe?g)$ {
add_header X-Whatever "Yo";
if ($webp_suffix != "") {
add_header Vary Accept;
}
}
}
This will result in both headers being set:
server {
location ~* ^/images/.+\.(png|jpe?g)$ {
if ($webp_suffix != "") {
add_header Vary Accept;
add_header X-Whatever "Yo";
}
}
}
EDIT 3: So now it's even more befuddling. It's like any prior add_header that isn't within the last code block (location or if statement) is completely ignored. i.e., with the following, only the X-Whatever header is set:
location ~ ^/assets/ {
gzip_static on;
expires max;
add_header Cache-Control public; # Ignored?!
add_header Last-Modified ""; # Ignored?!
add_header ETag ""; # Ignored?!
location ~* ^/assets/.+\.(?:png|gif|jpe?g)$ {
add_header X-Something "$uri$webp_suffix"; # Not ignored!
}
}
I had to remove the Vary Accept header condition and simply always apply it. I don't know if that's good or bad but I don't have any other choice considering it just removes every other header I've applied! I also had to move the webp location block above the assets block and duplicate code which sucks.
Working configuration:
server {
listen 80;
server_name assets.staging.pos staging.pos;
passenger_app_env staging;
passenger_ruby /home/vagrant/.rvm/gems/ruby-2.2.1#pos/wrappers/ruby;
passenger_enabled on;
error_log /home/vagrant/rails/staging.pos/log/nginx-error.log;
root /home/vagrant/rails/staging.pos/public;
# Try to return cached responses without hitting the app
try_files $uri /cache/$uri /cache/$uri.html #app;
location #app {
proxy_set_header X-Forwarded-Proto http;
}
# Return webp images when possible
location ~* ^/assets/.+\.(?:png|gif|jpe?g)$ {
expires max;
add_header Cache-Control public;
add_header Vary Accept;
add_header Last-Modified "";
add_header ETag "";
try_files $uri$webp_suffix $uri =404;
}
# Regular asset headers
location ~ ^/assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
add_header Last-Modified "";
add_header ETag "";
}
}

Nginx fall-through urls with caching

I have Rails app that has routes matching static files that are generated on first access.
Everything works fine if I have this block commented out in my site.conf:
location ^~ /uploads/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
Is there a way to have best of both worlds and have location block to be only activated if the actual file exists and fall-through if it isn't? Maybe add try inside?
One of best practices to serve response based on logic "local disk static file vs. dynamic response with backend" is try_files:
location ^~ /uploads/ {
gzip_static on;
expires max;
add_header Cache-Control public;
try_files $uri #backend;
}
location #backend {
proxy_pass ...
}
See official docs here.

HTTP_IF_MODIFIED_SINCE header not passed to Rails app (Nginx, Passenger)

I am trying to use the HTTP_IF_MODIFIED_SINCE header in my app to determine if resources are stale/fresh and render 200/304 in those cases.
In my dev environment everything works fine but I can't for the life of me get it to work in production.
I am using Passenger 3.0.11 and Nginx 1.0.13.
As you see below, I tried proxy_pass_header, proxy_set_header, passenger_pass_header and passenger_set_cgi_param. The last one actually sets a HTTP_IF_MODIFIED_SINCE header but it is empty...
Any help/ideas would be greatly appreciated!
My config:
server {
listen 80 default_server;
root /home/rails/myapp/current/public;
passenger_enabled on;
charset utf-8;
proxy_pass_header If-Modified-Since;
proxy_set_header If-Modified-Since $http_if_modified_since;
passenger_pass_header If-Modified-Since;
passenger_set_cgi_param HTTP_IF_MODIFIED_SINCE $http_if_modified_since;
if (-f $document_root/system/maintenance.html) {
rewrite ^(.*)$ /system/maintenance.html break;
}
location ~ \.(aspx|jsp|cgi)$ {
return 410;
}
location ~ ^/(assets)/ {
# http://guides.rubyonrails.org/asset_pipeline.html#server-configuration
# gzip_static on;
expires 1y;
add_header Cache-Control public;
add_header Last-Modified "";
add_header ETag "";
break;
}
}
to get it working with non-standard headers, containing underscores, do this inside your http or server block in the nginx.conf file:
underscores_in_headers on;
ignore_invalid_headers off;
and in the server block:
proxy_pass_header HTTP_IF_MODIFIED_SINCE
This can be useful if you have legacy HTTP headers which you need to deal with, and which contain underscores.
This was a user error after all. I sent the header to the app in the wrong format (IF_MODIFIED_SINCE instead of If-Modified-Since). After fixing that, it worked without any of the extra directives.

Resources