lahuang4
By:
lahuang4

Passing subdomains to a Node.js app running behind Nginx

June 18, 2017 130 views
Node.js Nginx Ubuntu 16.04

I'm trying to implement wildcard subdomains in my Node.js app -- for example, a user visiting subdomain.example.com will hit the app, in addition to example.com.

I currently have a Node app running on example.com, as well as an additional WordPress site running at example.com/blog. Nginx is acting as a reverse proxy for both of them (server block below). I've made changes locally for routing subdomains once they get to the Node app, and they seem to be working locally. But trying to access a subdomain on my Droplet just hangs. I've also previously tried setting up a Node app on a subdomain of the Droplet (ex. app.example.com) with similar lack of success -- the page just hangs. (I don't even get a 503 back.)

Something that I've noticed is that the server_name specified seems to be ignored; even without specifying one the routing still seems to happen normally.

Any help would be much appreciated!

# Point http requests to https
server {
    listen 80;

    server_name example.com www.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    include snippets/ssl-example.com.conf;
    include snippets/ssl-params.conf;

    root /var/www/html;
    index index.php index.html index.htm index.nginx-debian.html;

    server_name example.com *.example.com;

    location / {
        proxy_pass https://example.com:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location /blog {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    # For Lets Encrypt certbot
    location ~ /.well-known {
        allow all;
    }

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

    location ~ /\.ht {
        deny all;
    }

    location /favicon.ico { alias /var/www/html/example/favicon.ico; }
    location = /favicon.ico { log_not_found off; access_log off; }
    location = /robots.txt { log_not_found off; access_log off; allow all; }
# These lines seem to mess with serving static files on the Node app. Might need some other configuration
#    location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
#        expires max;
#        log_not_found off;
#    }

}
1 comment
  • Update: tried accessing the subdomain via another service provider and the page returns as not existing, rather than hanging.

3 Answers

Hi @lahuang4

Have you created a DNS record - either A or CNAME for *.example.com ?

Do you have a Wildcard certificate, because it seems like you're using Let's Encrypt, which doesn't support Wildcard, but requires that you generate a certificate for each domain.

And your current http-redirect, it's only redirecting to domains, but you probably want to redirect all to https, so it should look like this:

server {
    listen 80;
    listen [::]:80;
    server_name .example.com;
    return 301 https://$server_name$request_uri;
}

And unless your Node.js is running https and publicly accessible, which means you've also setup a certificate in Node.js and it's accessible directly without Nginx, then your proxy_pass should only look like this:

proxy_pass http://localhost:3000;
  • Thanks for the follow-up!

    I'm having someone check in on the DNS record and will update this when that's verified.

    I planned to enable HTTPS only on example.com and have HTTP only on subdomain.example.com, as a workaround for not having a wildcard certificate (but hadn't implemented it yet because I got sidetracked). Thanks for the reminder -- I'll make those changes and see how it goes.

    The Node.js is publicly accessible without Nginx and is using the Let's Encrypt certificate. For a subdomain, should I still be doing proxypass https://example.com:3000 or instead something like proxypass https://*.example.com:3000?

@lahuang4

Okay, then it's quite different. You cannot proxy_pass to a Wildcard (*), so you would pass to https://example.com:3000 and then in Node, you would have to implement something, so you know which subdomain is doing the query (look at header Host).

I would recommend keeping Node.js only listening on the localhost and then have Nginx handle the connection between the visitor and Node with the reverse proxy.
Then everything runs through Nginx and it's the one controlling what goes in and out.

And you would then have to setup 3 different server-blocks in Nginx.
One taking care of the http redirect of server_name example.com www.example.com.
One serving the https version of those.
And finally one serving the Wildcard *.example.com on http-only.

  • Thank you so much! The initial problem was indeed due to the missing wildcard DNS record.

    I'm including my final server blocks in case they're helpful to anyone in the future. I have the same app running an https server on port 3000 and an http server on port 4000.

    # Point http requests to https for example.com
    server {
        listen 80;
        listen [::]:80;
    
        server_name example.com www.example.com;
        return 301 https://$server_name$request_uri;
    }
    
    # https requests for example.com
    server {
        listen 443 ssl http2 default_server;
        listen [::]:443 ssl http2 default_server;
        include snippets/ssl-example.com.conf;
        include snippets/ssl-params.conf;
    
        root /var/www/html;
        index index.php index.html index.htm index.nginx-debian.html;
    
        server_name example.com;
    
        location / {
            proxy_pass https://example.com:3000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }
    
        location /blog {
            try_files $uri $uri/ /index.php$is_args$args;
        }
    
        # For Lets Encrypt certbot
        location ~ /.well-known {
            allow all;
        }
    
        location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/run/php/php7.0-fpm.sock;
        }
    
        location ~ /\.ht {
            deny all;
        }
    
        location /favicon.ico { alias /var/www/html/example/favicon.ico; }
        location = /favicon.ico { log_not_found off; access_log off; }
        location = /robots.txt { log_not_found off; access_log off; allow all; }
    
    }
    
    # http requests for example.com subdomains
    server {
        listen 80;
        listen [::]:80;
    
        server_name *.example.com;
    
        location / {
            proxy_pass http://example.com:4000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }
    
        location /favicon.ico { alias /var/www/html/example/favicon.ico; }
    }
    

    I also tried setting up a redirect from https --> http for subdomains, since I don't have a certificate for the subdomains. This doesn't seem to be working, all I get is the nginx startup page. I placed it under the example.com https requests server block. Is attempting to return http://$server_name$request_uri when server_name has a wildcard in it allowable?

    # Re-route https subdomain requests to http
    server {
        listen 443 ssl;
        listen [::]:443 ssl;
        include snippets/ssl-example.com.conf;
        include snippets/ssl-params.conf;
    
        server_name *.example.com
        return 301 http://$server_name$request_uri;
    }
    
Have another answer? Share your knowledge.