Using proxy_pass to forward http requests based on headers - nginx

I'm using a combination of ip6tables and nginx to process http requests from clients. The nginx server listens on port 8081 and must forward a request after examining the header.
Clients can send two types of requests:
GET/POST with no headers. These should be re-directed to https://jaguar.mydomain.com
GET/POST with specific header elb-jaguar.mydomain.com. These should be redirected to https://elb-jaguar.mydomain.com
When run as nginx -c /home/build/v6-only.conf, nginx fails because one server{} directive already has listen on port 8081
nginx: [emerg] duplicate listen options for [::]:8081 in /etc/nginx/v6/v6-only.conf:13
My config is as below:
server {
listen [::]:8081 ssl ipv6only=on;
server_name elb-jaguar.mydomain.com;
ssl_certificate /etc/ssl/elb.crt;
ssl_certificate_key /etc/ssl/elb.key;
location / {
proxy_pass https://elb-jaguar.mydomain.com:443;
}
}
server {
listen [::]:8081 ssl ipv6only=on;
ssl_certificate /etc/ssl/regular.crt;
ssl_certificate_key /etc/ssl/regular.key;
server_name jaguar.mydomain.com;
location / {
proxy_pass https://jaguar.mydomain.com:443;
}
}
How can I fix the above config to get the desired forwarding with proxy_pass?

Difficult to see because that setup should work.
But looking closer at the NGINX docs and your need for IPv6 only, it says (my emphasis):
ipv6only=on|off
this parameter (0.7.42) determines (via the IPV6_V6ONLY socket option) whether an IPv6 socket listening on a wildcard address [::] will accept only IPv6 connections or both IPv6 and IPv4 connections. This parameter is turned on by default. It can only be set once on start.
Because the error message complains of 'duplicate listen options', not 'already listening on that port' or similar, it suggests it is complaining about trying to set ipv6only a second time (even to the same value).
Also, it does say This parameter is turned on by default, so you could easily just remove it altogether, if only to try it.

Related

How to make nginx process only the specified host (alocal, blocal), but ignore the "naked" ip address?

I have two rules for nginx (local):
server_name alocal;
listen 80;
location / {
...
proxy_pass http://localhost:8081;
}
and
server_name blocal;
listen 80;
location / {
...
proxy_pass http://localhost:8082;
}
I also changed the "hosts" file.
C:\Windows\System32\Drivers\etc\hosts
127.0.0.1 alocal
127.0.0.1 blocal
Everything works well. When I make a request through the browser, I get the expected behavior.
http://alocal -> http://127.0.0.1:8081
http://blocal -> http://127.0.0.1:8082
But when I specify just "localhost" as a host, nginx still processes my request, and it takes the first rule that comes along (from those that I gave above).
http://localhost -> http://127.0.0.1:8081
or (depends on which rule comes first)
http://localhost -> http://127.0.0.1:8082
Why does nginx process localhost if other hosts (alocal, blocal) are specified in server_name?
How to make nginx process only the specified host (alocal, blocal), but ignore the "naked" ip address?
Nginx listening to Port 80. If there is a request not matching any server_name the default is taken. Either given by listen 80 default_server; or the first entry if omitted.
If you want to block all requests not matching the specified server_name or with empty Host-Header you need a catch all as last block:
server {
listen 80 default_server;
server_name "";
return 444;
}
It will be not really ignored, but rejected. You can't "ignore" something, nginx is blocking port 80 and handling all requests somehow.

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.

nginx port binding issues

Of course the port is already in use! hence my desire to redirect it! - I don't understand how I'm suppose to be able to redirect an app on 8787 to the https version if I can't start nginx due to this bind error?
nginx: configuration file /etc/nginx/nginx.conf test is successful
nginx: [emerg] bind() to 0.0.0.0:8787 failed (98: Address already in use)
server block:
server {
listen 8787;
listen [::]:8787 ipv6only=on;
server_name www.example.* example.* 45.224.123.199;
# SSL
ssl_certificate /etc/nginx/ssl/ssl-bundle.crt;
ssl_certificate_key /etc/nginx/ssl/sample.key;
port_in_redirect off;
return 302 https://example.com$request_uri;
}
Each application , in this case the upstream application and nginx, need their own unique ip and port binding pair. Generally, ONE application per IP can anybind.
You need to either:
choose a unique port for the proxy and upstream pairing (change nginx port, or change application port)
OR
chose a unique IP binding for your application.
Very often, a good practice is to application bind to the LAN ip instead of the public IP, to better isolate your application from the public internet.

How do I preserve the requested port when using proxy pass?

In the long run what I'm trying to do is to be able to connect to any domain through any port, for example, mysite.com:8000 and then through Nginx have it get routed to an internal ip through the same port. So for example to 192.168.1.114:8000.
I looked into iptables although I'm planning on having multiple domains so that really doesn't work for me in this case (feel free to correct me if I'm wrong). I made sure that the internal ip and port that I'm trying to access is connectable and running and also that the ports I'm testing with are accessible from outside my network.
Here's my Nginx config that I'm currently using:
server {
set $server "192.168.1.114";
set $port $server_port;
listen 80;
listen 443;
listen 9000;
server_name mysite.com;
location / {
proxy_pass http://$server:$port;
proxy_set_header Host $host:$server_port;
}
}
Currently what happens is that when I send a request it just times out. I've been testing using port 80 and also port 9000. Any ideas on what I might be doing wrong? Thanks!
EDIT:
I changed my config file to look like the following
server {
listen 9000;
server_name _;
location / {
add_header Content-Type text/html;
return 200 'test';
}
I keep getting the same exact error. The firewall is turned off so it just seems like Nginx isn't listening on port 9000. Any ideas on why that might be the case?
The most effective way would be to have three separate server directives, one for each port. That way, the upstream server isn't dynamic, so Nginx knows it can keep long-lived connections open to each one.
If you really don't want to do this, you might be able to get around it by doing something like this:
proxy_pass http://upstream_server.example:$server_port;
$port doesn't exist, but $server_port does, so that should work. (It's not $port because there are two ports for each connection: the server port and the client port, which are $server_port and $remote_port, respectively.)

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;
...
}

Resources