How to mutualize listen parameters for the same address:port pair in nginx? - nginx

I’m trying to deploy different websites on the same server that all listen on 0.0.0.0:443 with HTTP/2. My Ansible template looks like this:
# This is deployed in /etc/nginx/sites-available/{{ domain }}.conf and symlinked in sites-enabled
server {
listen 443 ssl http2 reuseport;
listen [::]:443 ssl http2 reuseport;
server_name {{ domain }};
...
}
This doesn’t work because according to the nginx doc (emphasis mine):
The listen directive can have several additional parameters specific to socket-related system calls. These parameters can be specified in any listen directive, but only once for a given address:port pair.
If I were deploying these websites by hand I would use listen 443 ssl http2 reuseport; in the first one and then listen 443; in the subsequent ones. But I’m trying to simplify the setup by having a single Ansible template that I can use for any website.
It looks cumbersome to check if there’s already a deployed website with these options and don’t include them if that’s the case. Also, if I remove the website with these options, it breaks all the others.
Is there any way I could add a unique file somewhere under /etc/nginx that says "use these parameters on 0.0.0.0:443" for all listen directives? I could probably add a dummy server{} block somewhere than listens for a unexisting server name but I wonder if there’s a proper way to do that.

This is the solution I have for now:
# /etc/nginx/sites-enabled/http2 -> /etc/nginx/sites-available/http2
server {
listen 443 ssl http2 reuseport;
listen [::]:443 ssl http2 reuseport;
server_name --;
}
-- is a dummy server name that doesn’t match anything. This block serves only as a common place for the listen parameters.
Now I just deploy websites with listen 443; and no other parameter, and they all share that common config.

Related

Nginx redirecting too many times with reverse proxy

I have a debian server with MySQL and Meilisearch, I want to use Nginx as a reverse proxy for future load balancing and also having TLS security.
I'm following Meilisearch's Documentation to set up Nginx with Meilisearch and Lets Encrypt with success, but they force Nginx to proxy everything to port 7700, I want to proxy to 3306 for MySQL, 7700 for Meilisearch, and 80 for an error page or a fallback web server. But after modifying /etc/nginx/nginx.conf, the website reloads too many times.
This is the configuration I'm trying at /etc/nginx/nginx.conf:
user www-data;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
stream {
upstream mysql {
server 127.0.0.1:3306;
}
upstream meilisearch {
server 127.0.0.1:7700;
}
server {
listen 6666;
proxy_pass mysql;
}
server {
listen 9999;
proxy_pass meilisearch;
}
}
http {
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name example.com;
return 301 https://\$server_name$request_uri;
}
server {
server_name example.com;
location / {
proxy_pass http://127.0.0.1:80;
}
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
# ssl_certificate managed by Certbot
# ssl_certificate_key managed by Certbot
}
}
The only difference is example.com is replaced by my domain, which has been already set to redirect to the server ip.
As ippi pointed out, it isn't necessary to reverse proxy MySQL in this particular case.
I used proxy_pass http://127.0.0.1:7700 as Meilisearch docs suggest.
For future DB load balancing, I'd use MySQL Clusters, and point out to them on another Nginx implementation that proxies everything (ie. HTTPS to web server, DB access to list of clusters, etc.).
Also, in this particular case, I don't actually require encrypted connections to the DB, but if I needed them, I'd use a self signed certificate in MySQL, and a CA certificate for the website, since my front ends communicate with a "central" Nginx proxy that could encrypt communication between backend servers and the proxy.
If I actually wanted to use the Let's Encrypt certificates for MySQL, I've found a good
recipe, but instead of copy pasting the certificate (which is painful) I'd mount the Let's Encrypt certificates' directory into MySQL's and change the permissions to 600 with chown. But then again, this is probably bad practice and would be better to use self signed certificates for MySQL as it's own docs suggest.

parametrize the server name in nginx conf

I have multiple domains all pointing to the same VM. (mydomain1.com, mydomain2.com, mydomain3.eu)
Now I have a huge nginx.conf which looks like this:
server {
listen 443 ssl;
server_name *.mydomain1.com;
ssl on;
ssl_certificate /etc/nginx/ssl/mydomain1.com.chained.crt;
ssl_certificate_key /etc/nginx/ssl/mydomain1.com.key;
# hundred more lines of rules
}
server {
listen 443 ssl;
server_name *.mydomain2.com;
ssl on;
ssl_certificate /etc/nginx/ssl/mydomain2.com.chained.crt;
ssl_certificate_key /etc/nginx/ssl/mydomain2.com.key;
# the same hundred more lines of rules
}
server {
listen 443 ssl;
server_name *.mydomain3.eu;
ssl on;
ssl_certificate /etc/nginx/ssl/mydomain3.eu.chained.crt;
ssl_certificate_key /etc/nginx/ssl/mydomain3.eu.key;
# the same hundred more lines of rules
}
Is there a way to shorten this - because currently I'm cloning such a huge code blcok when I have to add a new domain and only change the 3 lines where my domain name differs. I thought about some kind of parametrization, like (without knowing the correct syntax):
SERVER_NAME = {request_server_name}
ssl_certificate /etc/nginx/ssl/${SERVER_NAME}.chained.crt;
ssl_certificate_key /etc/nginx/ssl/${SERVER_NAME}.key;
is this possible? How?
There are two things you can do about it
Multi Domain SAN Certificate
You can purchase a multi domain SAN certificate. Which allows your to use different domains in the same certificate. So this way you wont have to have multiple blocks. In case you are using a self-signed certificate you can still create SAN certificate yourself
Use OpenRestry or Nginx+LUA
You can use OpenResty or Nginx with LUA support and use the ssl_certificate_by_lua_block directive of he same.
syntax: ssl_certificate_by_lua_block { lua-script }
context: server
phase: right-before-SSL-handshake
This directive runs user Lua code when NGINX is about to start the SSL handshake for the downstream SSL (https) connections.
It is particularly useful for setting the SSL certificate chain and the corresponding private key on a per-request basis. It is also useful to load such handshake configurations nonblockingly from the remote (for example, with the cosocket API). And one can also do per-request OCSP stapling handling in pure Lua here as well.
https://github.com/openresty/lua-nginx-module#ssl_certificate_by_lua_block
Also see below articles for some example implementations
https://medium.com/#mtourne/how-to-use-nginx-for-ssl-termination-for-any-domain-dc2e2c630058
https://blog.readme.io/auto-generating-ssl-certificates-for-custom-domains-using-lets-encrypt/

Can I enable HTTP/2 for specific server blocks (virtual hosts) only, on Nginx?

I have several virtual hosts on nginx.
Can I enable HTTP/2 for specific virtual hosts only on nginx?
When I enable HTTP/2 for a virtual host, like:
server {
listen 443 ssl http2;
server_name a.b.com;
...
}
I can access a.b.com by HTTP2.0.
But now every other virtual host on the same nginx supports HTTP/2 too.
But I want to access them only by HTTP/1.1.
Is the http2 directive at server level?
Short answer: not possible on your current setup.
When starting, nginx first creates a separate process for every group of virtual hosts that listen on the same IP:port combination, and then sets the capabilities of that process to be the sum of all capabilities of every virtual host in that group handled by said process.
In your case, there's only one process that handles all the virtual hosts bound to *:443, so the process includes the http2 capability.
In order to achieve what you want, you need to make nginx spawn a different process that doesn't have the http2 capability on a separate IP:port combination.
For the virtual hosts you want to be accessed via http2, you must either:
use a different port - trivial, just use another port for them (e.g. listen 8443 ssl http2;) and remove http2 from all the others
(e.g. `listen 443 ssl;)
use a different IP - you need to add another IP to the same NIC that uses your current IP and modify your virtual hosts accordingly
(e.g. listen new_ip:443 ssl http2; and listen current_ip:443 ssl;
respectively)
Example config for multiple IPs:
server {
listen current_ip:443 ssl;
server_name http11-host.example.com;
...
}
server {
listen current_ip:443 ssl;
server_name another-http11-host.example.com;
...
}
...
...
server {
listen new_ip:443 ssl http2;
server_name http2-host.example.net;
...
}
server {
listen current_ip:443 ssl http2;
server_name another-http2-host.example.org;
...
}

Nginx to serve secure and non secure traffic

We have secure and non-secure domains for our website e.g. secure.xyz.com and xyz.com
I used following link to make single server handle both port 80 and 443 traffic.
http://nginx.org/en/docs/http/configuring_https_servers.html#single_http_https_server
server {
listen 80;
listen 443 ssl;
server_name secure.xyz.com xyz.com;
....
ssl_certificate secure.xyz.com.crt;
ssl_certificate_key secure.xyz.com.key;
...
}
Every thing works fine except that $_SERVER's variable 'SERVER_NAME' is set to 'secure.xyz.com'. So that this hostname appears on non-secure page with 'http', e.g., 'http://secure.xyz.com'.
My queries are:
Does Nginx always pick the first server from the config ... irrespective of what client has requested? (and passes to proxy (php-fpm)?).
We have a lot of rules, so if we create two separate server (as per following), do I need to copy the rules in both places? Is there any maintainable way, like 'include /common_rules.conf'? I tried using 'location' rules (only) in the file, also I tried using 'server' encapsulating location rules however it does not work.
server {
listen 443;
server_name secure.xyz.com;
ssl on;
ssl_certificate secure.xyz.com.crt;
...
include common_rules.conf; ===>???
}
server {
listen 80;
server_name xyz.com;
...
include common_rules.conf; ===>???
}
Any help is highly appreciated.

nginx server_name wildcard or catch-all

I have an instance of nginx running which serves several websites. The first is a status message on the server's IP address. The second is an admin console on admin.domain.com. These work great. Now I'd like all other domain requests to go to a single index.php - I have loads of domains and subdomains and it's impractical to list them all in an nginx config.
So far I've tried setting server_name to * but that failed as an invalid wildcard. *.* works until I add the other server blocks, then I guess it conflicts with them.
Is there a way to run a catch-all server block in nginx after other sites have been defined?
N.B. I'm not a spammer, these are genuine sites with useful content, they're just powered by the same CMS from a database!
Change listen option to this in your catch-all server block. (Add default_server) this will take all your non-defined connections (on the specified port).
listen 80 default_server;
if you want to push everything to index.php if the file or folder does not exist;
try_files $uri /$uri /index.php;
Per the docs, It can also be set explicitly which server should be default, with the **default_server** parameter in the listen directive
As a convention, the underscore is used as a server name for default servers.
From http://nginx.org/en/docs/http/server_names.html
In catch-all server examples the strange name “_” can be seen:
server {
listen 80 default_server;
server_name _;
return 444;
}
There is nothing special about this name, it is just one of a myriad of >invalid domain names which never intersect with any real name. Other >invalid names like “--” and “!##” may equally be used.
Note that server_name _; alone is not enough. The above example only works because of default_server in the listen directive.
This will work:
server_name ~^(.+)$
Now you can use mask:
server {
listen 80;
server_name *.example.org;
...
}
server {
listen 80;
server_name mail.*;
...
}
Look more here: http://nginx.org/en/docs/http/server_names.html
Only 1 server directive
From Nginx listen Docs
The default_server parameter, if present, will cause the server to
become the default server for the specified address:port pair. If none
of the directives have the default_server parameter then the first
server with the address:port pair will be the default server for this
pair.
If you only have 1 server directive, that will handle all request, you don't need to set anything.
Multiple server directive
If you want to match all request with specified server directive, just add default_server parameter to listen, Nginx will use this server directive as default.
server {
listen 80 default_server;
}
About server_name _;
From Nginx Docs
In catch-all server examples the strange name “_” can be seen:
server {
listen 80 default_server;
server_name _;
return 444;
}
There is nothing special about this name, it is just one of a myriad
of invalid domain names which never intersect with any real name.
Other invalid names like “--” and “!##” may equally be used.
It doesn't matter what server_name you set, it is just an invalid domain name.
For me somehow define default_server was not working. I solved it by
server_name ~^.*$
using regular expression of all.
If you also want to catch requests with empty Host header (which is allowed in HTTP/1.0) you can use both regex and empty server_name:
server {
listen 80;
server_name ~. "";
}
Try $http_host
server {
server_name $http_host;
}

Resources