NodeRED websockets and Nginx reverse proxy (subfolder)

Hello everyone,

I'm writting this post because I'm trying to access nodeRED behind my Nginx reverse proxy, unfortunately I can't get the websockets to work. NodeRED load correctly but I get an error 502 and the below line appear when I debug the reverse proxy.

upstream prematurely closed connection while reading response header from upstream

This is my NGINX config :

location ^~ /nodered/ {
        resolver 127.0.0.11 valid=30s;
        set $upstream_app 192.168.1.119;
        set $upstream_port 1880;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
        error_log /config/log/nginx/nodered_error.log debug;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version  1.1;
        proxy_cache_bypass  $http_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $proxy_protocol_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_read_timeout 300s;
        proxy_connect_timeout 75s;

        rewrite ^/nodered/(.*)$ /$1 break;
    }

    location ^~ /nodered/comms {
        resolver 127.0.0.11 valid=30s;
        set $upstream_app 192.168.1.119;
        set $upstream_port 1880;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
        error_log /config/log/nginx/comms_error.log debug;

        
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version  1.1;
        proxy_cache_bypass  $http_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $proxy_protocol_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_set_header Upgrade "websocket";
        proxy_read_timeout 300s;
        proxy_connect_timeout 75s;
    }

If anyone has an idea ..

Regards,
Thomas

Mine looks a little different but then my config is different.

I use separate include files. I include a common_proxy_headers.conf for all paths and a common_ws_proxy_headers.conf file for Node-RED paths.

common_proxy_headers.conf

# Common reverse proxy settings
# Don't forget to also add proxy_pass url;

  proxy_set_header Forwarded "by=$host;for=$proxy_add_x_forwarded_for;host=$host;proto=$scheme";
  proxy_set_header Via       "$scheme/1.1 $host:$server_port";

  proxy_set_header X-Real-IP         $remote_addr;
  proxy_set_header Host              $host;
  proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header X-Real-IP         $remote_addr;
  proxy_set_header X-Forwarded-Port  $server_port;
  proxy_set_header X-Forwarded-Host  $host;

  # Proxy timeouts
  proxy_connect_timeout       60s;
  proxy_send_timeout          60s;
  proxy_read_timeout          60s;

  # If proxied responses happening too slowly, try turning off the buffering
  #proxy_buffering off;

common_ws_proxy_headers.conf

# Common headers for proxy of websockets

proxy_http_version          1.1;
proxy_set_header Upgrade    $http_upgrade;
proxy_set_header Connection "upgrade";

I'm using the same parameters as you, it's weird Nginx should be able to forward the websocket requests

Do you have Node-RED set to trust the proxy?

Oh, didn't know that it will have to be made. How can I do that ?

Also, I had the same problem when I used no authentication for nodeRED

It is in settings.js - there are several things you can change:

    /** By default, the Node-RED UI accepts connections on all IPv4 interfaces.
     * To listen on all IPv6 addresses, set uiHost to "::",
     * The following property can be used to listen on a specific interface. For
     * example, the following would only allow connections from the local machine.
     * This can be useful security when putting NR behind a reverse proxy on the same device.
     */
    //uiHost: process.env.HOST || '127.0.0.1',

    /** The following property can be used to pass custom options to the Express.js
     * server used by Node-RED. For a full list of available options, refer
     * to http://expressjs.com/en/api.html#app.settings.table
     */
    httpServerOptions: {
        // http://expressjs.com/en/api.html#trust.proxy.options.table
        'trust proxy': '127.0.0.1/8, ::1/128', //true,  // true/false; or subnet(s) to trust; or custom function returning true/false. default=false
        'x-powered-by': false,
    },

    // I think you can do this instead of setting the server options
    // - I prefer to set the ExpressJS server options directly
    /** If you need to set an http proxy please set an environment variable
     * called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system.
     * For example - http_proxy=http://myproxy.com:8080
     * (Setting it here will have no effect)
     * You may also specify no_proxy (or NO_PROXY) to supply a comma separated
     * list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk
     */

If you use uibuilder with its optional custom server, that also has server options and you can add the same trust proxy option to it.

Added the trusted proxy.
Still not working, connection refused...
I don't understand.

You might need to try to up the node-red logging to trace level to see if something reports why it is doing that.

Might also be worth setting up a different proxy temporarily - that's to say, a simplified location.

Here is the relevant section of my config that I didn't share before in case it helps:

# Deal with all other Node-RED user endpoints - e.g. uibuilder
# Takes over the whole root url which means you can't use it for anything else
# Better to set httpNodeRoot to something (e.g. 'nr') and then just proxy that (e.g. '/nr/')
location / {
  # A full set of headers have to be redone for every context that defines another header
  include /etc/nginx/conf.d/includes/common_security_headers.conf;

  # Reverse Proxy
  #proxy_pass https://localhost:1880/; # <== CHANGE TO MATCH Node-RED's base URL
  include /etc/nginx/conf.d/includes/common_proxy_headers.conf;

  
  # Proxy the Node-RED Editor https://my.public.domain/red/ to http://localhost:1880/red/
  location /red/ {
    
    # ==> Of course, you could have a separate auth here! <==

    # Reverse Proxy for websockets
    include /etc/nginx/conf.d/includes/common_ws_proxy_headers.conf;

    # Reverse Proxy
    proxy_pass https://localhost:1880/red/; # <== CHANGE TO MATCH THE EDITOR's URL
    
    # Tell upstream which proxy was used
    proxy_set_header X-JK-Proxy "RED";
    # Tell client which proxy was used
    add_header X-JK-Proxy "RED";

    # Proxy the Node-RED Dashboard https://my.public.domain/red/dash/ to http://localhost:1880/ui/
    location  /red/dash/ {

      # ==> Feel free to have different auth here <==

      # Reverse Proxy
      proxy_pass https://localhost:1880/ui/; # <== CHANGE TO MATCH THE EDITOR's URL
      
      # Tell upstream which proxy was used
      proxy_set_header X-JK-Proxy "RED-DASH";
      # Tell client which proxy was used
      add_header X-JK-Proxy "RED-DASH";
    }

    # Proxy the Node-RED Dashboard https://my.public.domain/red/ui/ to http://localhost:1880/ui/
    location  /red/ui/ {

      # ==> Feel free to have different auth here <==

      # Reverse Proxy
      proxy_pass https://localhost:1880/ui/; # <== CHANGE TO MATCH THE EDITOR's URL
      
      # Tell upstream which proxy was used
      proxy_set_header X-JK-Proxy "RED-UI";
      # Tell client which proxy was used
      add_header X-JK-Proxy "RED-UI";
    }
  }
}

And the security headers:

# Default header properties
# These have to be respecified for EVERY server/location context if that context defines another header. 
# So we use an include file that won't be loaded by the nginx.conf file directly.

# don't allow the browser to render the page inside an frame or iframe and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking
# if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
add_header X-Frame-Options SAMEORIGIN;

# when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
# to disable content-type sniffing on some browsers. https://www.owasp.org/index.php/List_of_useful_HTTP_headers
add_header X-Content-Type-Options nosniff;

# This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
# It's usually enabled by default anyway, so the role of this header is to re-enable the filter for 
# this particular website if it was disabled by the user.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
add_header X-XSS-Protection "1; mode=block";

# Content Security Policy (CSP) -  tell the browser that it can only download content from the domains you explicitly allow
# http://www.html5rocks.com/en/tutorials/security/content-security-policy/
# https://www.owasp.org/index.php/Content_Security_Policy
# Must be configured for your specific needs
#add_header Content-Security-Policy ........ ;

# You may want this in case something tries to refer from your site to something like Facebook https://scotthelme.co.uk/a-new-security-header-referrer-policy/
add_header Referrer-Policy "strict-origin-when-cross-origin";

# Configure for Strict Transport Security (HSTS) - only if https is in use - see map in default.conf
add_header Strict-Transport-Security $sts;

# NGINX seems to often ignore directive to not blab
add_header server 'home';

I don't actually open my home server to the Internet so it is OK for me to share these :grinning:
And even if I did, I would only allow a connection from Cloudflare which I would use as a further layer of web protection.

I want to say thank you very much, I used your config and it worked, I think the thing that cause trouble is that I made an other location for the '/comms/' URL.
This is my working config :

    location ^~ /nodered/ {
        resolver 127.0.0.11 valid=30s;
        set $upstream_app 192.168.1.119;
        set $upstream_port 1880;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
        error_log /config/log/nginx/nodered_error.log debug;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Forwarded "by=$host;for=$proxy_add_x_forwarded_for;host=$host;proto=$scheme";
        proxy_set_header Via       "$scheme/1.1 $host:$server_port";

        proxy_set_header X-Real-IP         $proxy_protocol_addr;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port  $server_port;
        proxy_set_header X-Forwarded-Host  $host;
        proxy_http_version          1.1;
        proxy_set_header X-JK-Proxy "RED";
        add_header X-JK-Proxy "RED";

        # Proxy timeouts
        proxy_connect_timeout       60s;
        proxy_send_timeout          60s;
        proxy_read_timeout          60s;

        rewrite ^/nodered/(.*)$ /$1 break;

Thanks again, have a great day !

1 Like

Glad it works. You probably don't need my custom header though :grinning:

proxy_set_header X-JK-Proxy "RED";
add_header X-JK-Proxy "RED";

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.