How to use nginx as reverse proxy with multiple IPs and SSL?

I would like to setup nginx reverse proxy with multiple domains and an IP for each of them to use different SSL Certs. I run Ubuntu as OS which is installed on a KVM/Qemu VM.

As I understand nginx, it should be able serve one domain (and the subdomains belonging to it) via one IP. But I can't get it to run ...

That's my nginx config:

/etc/nginx/sites-enabled/my_first_domain

server {
  listen x.x.x.84:80;                                   # this is a public ip
  server_name firstdomain.com;

  access_log /var/log/nginx/access.log proxy;     # I made my own logformat
  error_log  /var/log/nginx/error.log;

  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header Client-IP $remote_addr;
  proxy_set_header X-Host $host;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-For $remote_addr;

  location / {
    rewrite ^/(.*) https://firstdomain/$1;  # redirect to https
  }
}

server {
  listen x.x.x.84:443 ssl;                            # this is a public ip
  server_name firstdomain.com;

  ssl_certificate      /etc/nginx/ssl/combined.firstdomain.com.crt;
  ssl_certificate_key  /etc/nginx/ssl/wildcard.firstdomain.com.key;

  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header Client-IP $remote_addr;
  proxy_set_header X-Host $host;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-For $remote_addr;

  access_log /var/log/nginx/access.log proxy;
  error_log  /var/log/nginx/error.log;

  location / {
    proxy_pass http://x.x.x.85;                       # this is a public ip, too 
    proxy_redirect off;
  }
}

This config is very plain, I think. Every request on port 80 should be redirected to port 443. The configuration for a second domain is very similar.

/etc/nginx/sites-enabled/anotherdomain

server {
  listen x.x.x.87:80;                                   # this is a public ip
  server_name anotherdomain.org;

  access_log /var/log/nginx/access.log proxy;     # I made my own logformat
  error_log  /var/log/nginx/error.log;

  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header Client-IP $remote_addr;
  proxy_set_header X-Host $host;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-For $remote_addr;

  location / { 
    rewrite ^/(.*) https://anotherdomain.org/$1;  # redirect to https
  }   
}

server {
  listen x.x.x.87:443 ssl;                            # this is a public ip
  server_name anotherdomain.org;

  ssl_certificate      /etc/nginx/ssl/combined.anotherdomain.org.crt;
  ssl_certificate_key  /etc/nginx/ssl/wildcard.anotherdomain.org.key;

  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header Client-IP $remote_addr;
  proxy_set_header X-Host $host;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-For $remote_addr;

  access_log /var/log/nginx/access.log proxy;
  error_log  /var/log/nginx/error.log;

  location / {
    proxy_pass http://x.x.x.89;                       # this is a public ip, too
    proxy_redirect off;
  }
}

My netstat -tulpen snippet:

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       User       Inode       PID/Program name
...
tcp        0      0 x.x.x.84:80           0.0.0.0:*               LISTEN      0          8724        1187/nginx      
tcp        0      0 x.x.x.87:80           0.0.0.0:*               LISTEN      0          8723        1187/nginx      
tcp        0      0 x.x.x.84:443          0.0.0.0:*               LISTEN      0          8726        1187/nginx      
tcp        0      0 x.x.x.87:443          0.0.0.0:*               LISTEN      0          8725        1187/nginx      
...

Actually I thought that should be enough to host multiple domains with SSL on the same server. But nginx servs the same certificate on each request. The result is an SSL error.

And there is another unexpected behavior. While debugging I tried to get the websites with telnet as client. This request:

user@host:~$ telnet x.x.x.84 80
Trying x.x.x.84...
Connected to x.x.x.84.
Escape character is '^]'.
GET / HTTP/1.1
Host: firstdomain.com

belongs to this response:

HTTP/1.1 302 Moved Temporarily
...
Location: https://firstdomain.com/

Mmh, that's fine ... but this request [same domain (see 'Host:'-header), but the IP is not right now]:

user@host:~$ telnet x.x.x.87 80
Trying x.x.x.87...
Connected to x.x.x.87.
Escape character is '^]'.
GET / HTTP/1.1
Host: firstdomain.com

... results in an delivery of the website I'm asking for. So I got the website through the proxy although I sent the request to the wrong IP and without SSL. That is exactly what I wanted to prevent!

Thank you for your ideas!


Solution 1:

Your first configuration should look like this.

server {
  listen x.x.x.84:80;
  server_name firstdomain.com;

  access_log /var/log/nginx/access.log proxy;
  error_log  /var/log/nginx/error.log;

  return https://$server_name$request_uri;
}

server {
  listen x.x.x.84:443 ssl;
  server_name firstdomain.com;
  root ????;

  ssl_certificate      /etc/nginx/ssl/combined.firstdomain.com.crt;
  ssl_certificate_key  /etc/nginx/ssl/wildcard.firstdomain.com.key;

  access_log /var/log/nginx/access.log proxy;
  error_log  /var/log/nginx/error.log;

  location / {
    # Do not proxy everything to the backend, deliver static files
    # right away!
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Client-IP $remote_addr;
    proxy_set_header X-Host $host;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_redirect off;
    # The backend MUST be SSL enabled as well!
    proxy_pass https://x.x.x.85;
  }
}

Your second configuration should look like this.

server {
  listen x.x.x.87:80;
  server_name anotherdomain.org;

  access_log /var/log/nginx/access.log proxy;
  error_log  /var/log/nginx/error.log;

  return https://$server_name$request_uri;
}

server {
  listen x.x.x.87:443 ssl;
  server_name anotherdomain.org;
  root ????;

  ssl_certificate      /etc/nginx/ssl/combined.anotherdomain.org.crt;
  ssl_certificate_key  /etc/nginx/ssl/wildcard.anotherdomain.org.key;

  access_log /var/log/nginx/access.log proxy;
  error_log  /var/log/nginx/error.log;

  location / {
    # Do not proxy everything to the backend, deliver static files
    # right away!
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Client-IP $remote_addr;
    proxy_set_header X-Host $host;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_redirect off;
    # The backend MUST be SSL enabled as well!
    proxy_pass https://x.x.x.85;
  }
}

Please let me know if this helps so we can redefine the configuration further.