2

I'm trying to route requests in Nginx in the following way:

  1. Requests made to / should go to a PHP script (proxy.php, which itself is a proxy)
  2. Requests made to /websocket should be proxied to http://localhost:4000/websocket
  3. All other requests should be proxied to http://localhost:4000/

I could get 2. and 3. to work using the following config:

server {
    listen 443 ssl;

    server_name proxy.domain.com;

    ssl_certificate /etc/nginx/ssl/proxy.domain.com/468446/server.crt;
    ssl_certificate_key /etc/nginx/ssl/proxy.domain.com/468446/server.key;

    location = /websocket/ {
        proxy_pass http://127.0.0.1:4000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:4000;
    }

}

Then I tried to figure out a way to add 1. and came up with the following:

server {
    listen 443 ssl;

    server_name proxy.domain.com;

    root /var/www/dowmain;

    ssl_certificate /etc/nginx/ssl/proxy.domain.com/468446/server.crt;
    ssl_certificate_key /etc/nginx/ssl/proxy.domain.com/468446/server.key;

    location = /websocket/ {
        proxy_pass http://127.0.0.1:4000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:4000;
    }

    location = / {
        include fastcgi_params;
        fastcgi_param   SCRIPT_FILENAME  $document_root/proxy.php;
        fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
    }

}

However, with this configuration, 2. does not work anymore. Requests made to /websocket are processed by the PHP script. Seems like the /websocket location block is not working anymore.

Interestingly, if I switch the config to http://, everything works fine.

Any idea what I'm doing wrong?

UPDATE

I think I can rule out the location /websocket { ... } as the source of the issue, because if I replace the PHP config stuff in the location = / { ... } block with the rules from the location / { ... } block, it works fine (but is not what I need). So I suspect PHP, that messes it all up.

UPDATE II

It even works on my local machine with certificates from mkcert, with the exact same config.

So the only difference is the Let's Encrypt certificate vs the local one. Even the Nginx and PHP Versions are the basically the same (PHP 7.2.7, Nginx 1.15.1 on my local machine vs. PHP 7.2.13, Nginx 1.15.6)

2 Answers2

3

It seems like an issue with trailing slash at the and of location which contains proxy_pass directive. You can try adding it to your websocket location:

location /websocket/ {
    proxy_pass http://127.0.0.1:4000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Also because of this I removed /websocket/ from proxy_pass directive since in you substitute "/webconfig" part of URI to "/webconfig/" which is confusing

If the proxy_pass directive is specified with a URI, then when a request is passed to the server, the part of a normalized request URI matching the location is replaced by a URI specified in the directive.

To the main question - based on Nginx documentation:

If a location is defined by a prefix string that ends with the slash character, and requests are processed by one of proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass, or grpc_pass, then the special processing is performed. In response to a request with URI equal to this string, but without the trailing slash, a permanent redirect with the code 301 will be returned to the requested URI with the slash appended.

I suppose that in your case, websocket location doesn't have a trailing slash and it's being 301 redirected to the / location by this rule.

Shanjohn
  • 66
  • 2
  • Thanks for your answer, but unfortunately that doesn't solve the issue. I also tried with `location = /websocket/ { ... }` but no luck as well. – Rico Leuthold Jan 09 '19 at 08:41
  • You can try to enable debug log and it will show you the process of how location has been chosen on request. Also it is strange that everything work if you use http, the behavior should be the same. Have you tried disabling http2? – Shanjohn Jan 09 '19 at 11:33
  • I did that as well - everything works and looks fine until I configure the `location = / {...}` block for PHP. – Rico Leuthold Jan 12 '19 at 11:28
  • I still don't get why it is happening, but since everything works with http you can use a workaround - create a main server block which will listen https and proxypass all request to http server block (which should listen 127.0.0.1:80) and process all of your locations there – Shanjohn Jan 15 '19 at 11:58
  • Or another hack - stop using = for php location and start using regex for another one – Shanjohn Jan 15 '19 at 12:08
  • location ^~ /websocket/ { proxy_pass http://127.0.0.1:4000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location ~* ^/.* { proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_pass http://127.0.0.1:4000; } location / { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root/proxy.php; fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; } – Shanjohn Jan 15 '19 at 12:11
1

Your configuration looks fine to me, I double checked it in my nginx setup and it does what it needs to do. I checked by adding headers in the different locations:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name proxy.domain.com;
    root /var/www/dowmain;

    ssl_certificate /etc/nginx/ssl/proxy.domain.com/468446/server.crt;
    ssl_certificate_key /etc/nginx/ssl/proxy.domain.com/468446/server.key;

    ssl_protocols TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/dhparams.pem;

    location /websocket {
        proxy_pass http://127.0.0.1:4000/websocket/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        add_header X-location websocket always;
    }

    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:4000;
        add_header X-location wildcard always;
    }

    location = / {
        include fastcgi_params;
        fastcgi_param   SCRIPT_FILENAME  $document_root/proxy.php;
        fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
        add_header X-location root always;
    }
}

And then inspect the headers with:

$ curl -I https://proxy.domain.com
$ curl -I https://proxy.domain.com/anythingelse
$ curl -I https://proxy.domain.com/websocket

Output should contain:

X-location: websocket
Or:
X-location: wildcard
Or:
X-location: root

The value of X-location should match whatever you have set in your nginx configuration for that block.

My nginx version is:

# nginx -v
nginx version: nginx/1.10.1
Chone
  • 11
  • 1
  • Thanks for taking the time to look into this. As I wrote in the comment of the other answer, everything works fine, until I add the PHP config. – Rico Leuthold Jan 12 '19 at 11:29
  • I have tried and this works including PHP in my setup. You should check with the headers whether you are in the correct block. I think you experience a different problem and you should eliminate this first. – Chone Jan 12 '19 at 19:46