Adventures in nginx!

| #linux | #nginx | #Tech |

Mostly for my own benefit, a log of shit I have to deal with in nginx, and the solutions to same.

Dealing with slash-delimited parameters in URLs

On my previous system, URLs with an index file followed by /slash/separated/parameters were gracefully handled. The index file was called, the params were available by parsing the URL used to get there.

But on the new linux/nginx server, all I got was a 404, because index.php was just another directory in the URL structure. I searched for solutions, found a stack of them, but this is the one that worked (source):

location ~ [^/]\.php(/|$) {
  fastcgi_split_path_info  ^(.+\.php)(/.+)$;
  fastcgi_index            index.php;
  fastcgi_pass    (or your php-fpm socket);
  include                  fastcgi_params;
  fastcgi_param   PATH_INFO       $fastcgi_path_info;
  fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;

Dealing with the same thing for wordpress

The same solution for WordPress was much simpler, and this is where I started (source lost, soz):

try_files $uri $uri/ /wordpress/index.php?q=$uri&$args;

Super simple password access

nginx doesn’t deal with .htaccess files, like Apache does, so there’s no easy way to add user:pass files to control access to your stuff. But, happily, you can achieve the same thing with any old password file. Just be sure to put it somewhere outside the /html/root/ or your crafty users will find a way to download it.

File contents:



  location /premium/ {
    auth_basic "Restricted Content";
    auth_basic_user_file /var/www/nfg/cgi-bin/data/passwd;

Forcing HTTPS

The world has changed. Push all your users from unencrypted HTTP to super great HTTPS. Here’s how to use a 301 redirect to tell the user’s browser that all HTTP requests have moved permanently to HTTPS:

server {
  listen 80;
  location / { return 301 https://$host$request_uri; }

Proper IPs in Logs Behind a Reverse Proxy

Because I’m running several servers here, some of them are behind a reverse proxy. The first nginx machine basically directs appropriate traffic to the other servers, based on the domain name. Unfortunately, the receiving nginx servers don’t deal with the originating IP address by default, instead they log the reverse proxy’s IP address, so my log files show 100% of the traffic coming from one machine on the local LAN. Hardly ideal.

Here’s my new logging config, which is basically the standard nginx config, with the first string replaced by $http_x_real_ip. The first directive defines the format as ‘nfg’, and the next one tells nginx where to write the file, and to use the nfg format. (A lot of internet instructions tell you to use ‘combined’ here but that’s a reserved name and nginx will refuse to run if you do this). If you define this in the main nginx.conf file, you can simply use the access_log directive with the new format name for your individual site configs (if that’s how you roll).

log_format nfg '$http_x_real_ip - $remote_user [$time_local] '
  '"$request" $status $body_bytes_sent '
  '"$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log nfg;

Reverse Proxy Config

I’m migrating my servers in stages. The Linux box is assuming more responsibility in increments, and the easiest way to do this was to configure nginx as a reverse proxy. Basically, it’s a gatekeeper, handling traffic itself when required, but shuffling all requests to and from other servers.

For simplicity, I use another file containing all the proxy stuff, so that I don’t have to maintain the same config in a dozen files. Instead, simply include the file every time it’s needed. This bit of code will load proxy.conf file from the /etc/nginx/ directory (not sites-enabled!) when needed:

    location /mb {
      include proxy.conf;

Now of course you can paste the contents of that file in your config file, and if you’ve only got one, that’s easiest, but I’ve got like twelve, so… Anyway, here’s the configuration I’m using:

  location / {
    proxy_pass_header Authorization;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering off;
    client_max_body_size 0;
    proxy_read_timeout 36000s;
    proxy_redirect off;
    proxy_set_header X-Forwarded-Proto https;
    fastcgi_param REMOTE_ADDR $http_x_real_ip;

Several things to note here.

Check out the last three lines:

– The https on the proxy_pass directive. That’s so communications across the LAN are also encrypted, but it also means that the receiving server is handling secure requests. Without this, it thinks it’s insecure, and services like forums or WordPress will set all the links to be http, which causes grief for the client. Setting this to HTTPS makes it all work better.

– However! A reverse proxy using HTTPS from proxy to server means the receiving server must have its own SSL certs installed (so these two machines can talk securely), so you can either copy them from the proxy, or create your own. The nginx proxy, I’m told (I haven’t tested it), doesn’t check the cert validity, so if you DIY it won’t throw errors because it’s not from a recognized authority.

– The X-Forwarded-Proto line was critical to the success of this config, but it doesn’t seem to be required for everyone.

WordPress absolutely freaked out when trying to run HTTPS behind a proxy. Adding the following line (source) to the wp-config.php file solved it (and you’ll notice it relies on the setting I just mentioned):

if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') $_SERVER['HTTPS']='on';

– The last line: without that, the receiving server won’t also receive the actual source IP, so PHP and all your logs will report 100% of the traffic comes from the proxy server. It breaks a lot of stuff.

I use Aprelium’s Abyss server on my Windows machine. In order for it to work behind the reverse proxy, it needs a few extra tweaks.

Proxy Exceptions:

I have several services running on the reverse proxy itself, not the older server, and this is how I make exceptions to the proxy rule:

  location ^~ /rss {
    location ~* \.php$ {
      include snippets/fastcgi-php.conf;
      fastcgi_pass unix:/run/php/php7.0-fpm.sock;

This will exclude the ‘rss’ directory from proxy redirection. Of course, you must be running the appropriate local root and index file directives, as you would for any local access.

[ Mar 11 2018 ]

Got something to add?

Your Comment