I am running a few Virtual Box'es on my host - one of them is from OwnCloud. This allowed me to set it up really fast, and using PhpVirtualBox, I also have a nice tool for management.
I already know how to forward HTTP traffic to my VMs. They are all set up as NAT with their ports forwarded by scheme:
1VNNN
1: The master-range
V: Virtual Box ID. Basically an ID i choose.
N: Port
That means I have 12080 and 12443 for the OwnCloud box. And now I would like to forward HTTPS traffic. Here is what I did with regular HTTP:
nginx/sites-available/owncloud:
server {
listen (...some ip...):80;
server_name cloud.example.com;
# Proxy
set $REMOTE_PORT 12080;
include basic_proxy;
}
nginx/basic_proxy:
# Proxy
# Really basic, does HTTP stuffles.
# #var $REMOTE_PORT : Remote HTTP port
location / {
proxy_pass http://127.0.0.1:$REMOTE_PORT;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
And now I'd like to do it the HTTPS way. What'd be a good way to do so?
I am likely going to share a lot of configs like for proxy, PHP apps and such, which is why I made this into an external file immediately. Modularity is pretty useful.
Related
I am currently working on a FPV robotics project that has two servers, flask/werkzeug and streamserver, serving http traffic and streaming video to an external web server, located on a different machine.
The way it is currently configured is like this:
http://1.2.3.4:5000 is the "web" traffic (command and control) served by flask/werkzeug
http://1.2.3.4:5001 is the streaming video channel served by streamserver.
I want to place them behind a https reverse proxy so that I can connect to this via https://example.com where "example.com" is set to 1.2.3.4 in my external system's hosts file.
I would like to:
Pass traffic to the internal connection at 1.2.3.4:5000 through as a secure connection. (certain services, like the gamepad, won't work unless it's a secure connection.)
Pass traffic to 1.2.3.4:5001 as a plain-text connection on the inside as "streamserver" does not support HTTPS connections.
. . . such that the "external" connection (to ports 5000 and 5001 are both secure connections as far as the outside world is concerned, such that:
[external system]-https://example.com:5000/5001----nginx----https://example.com:5000
\---http://example.com:5001
http://example.com:5000 or 5001 redirects to https.
All of the literature I have seen so far talks about:
Routing/load-balancing to different physical servers.
Doing everything within a Kubernates and/or Docker container.
My application is just an every-day plain vanilla server type configuration, and the only reason I am even messing with https is because of the really annoying problems with things not working except in a secure context which prevents me from completing my project.
I am sure this is possible, but the literature is either hideously confusing or appears to talk to a different use case.
A reference to a simple how-to would be the most usefull choice.
Clear and unambiguous steps would also be appreciated.
Thanks in advance for any help you can provide.
This minimal config should provide public endpoints:
http://example.com/* => https://example.com/*
https://example.com/stream => http://1.2.3.4:5001/
https://example.com/* => https://1.2.3.4:5000/
# redirect to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com
www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com
www.example.com;
ssl_certificate /etc/nginx/ssl/server.cer;
ssl_certificate_key /etc/nginx/ssl/server.key;
location /stream {
proxy_pass http://1.2.3.4:5001/; # HTTP
}
# fallback location
location / {
proxy_pass https://1.2.3.4:5000/; # HTTPS
}
}
First, credit where credit is due: #AnthumChris's answer is essentially correct. However, if you've never done this before, the following additional information may be useful:
There is actually too much information online, most of which is contradictory, possibly wrong, and unnecessarily complicated.
It is not necessary to edit the nginx.conf file. In fact, that's probably a bad idea.
The current open-source version of nginx can be used as a reverse proxy, despite the comments on the nginx web-site saying you need the Pro version. As of this instant date, the current version for the Raspberry Pi is 1.14.
After sorting through the reams of information, I discovered that setting up a reverse proxy to multiple backend devices/server instances is remarkably simple. Much simpler than the on-line documentation would lead you to believe.
Installing nginx:
When you install nginx for the first time, it will report that the installation has failed. This is a bogus warning. You get this warning because the installation process tries to start the nginx service(s) and there isn't a valid configuration yet - so the startup of the services fails, however the installation is (likey) correct and proper.
Configuring the systems using nginx and connecting to it:
Note: This is a special case unique to my use-case as this is running on a stand-alone robot for development purposes and my domain is not a "live" domain on a web-facing server. It is a "real" domain with a "real" and trusted certificate to avoid browser warnings while development progresses.
It was necessary for me to make entries in the robot's and remote system's HOSTS file to automagically redirect references to my domain to the correct device, (the robot's fixed IP address), instead of directnic's servers where the domain is parked.
Configuring nginx:
The correct place to put your configuration file, (on the raspberry pi), is /etc/nginx/sites-available and create a symlink to that file in /etc/nginx/sites-enabled
It does not matter what you name it as nginx.conf blindly imports whatever is in that directory. The other side of that is if there is anything already in that directory, you should remove it or rename it with a leading dot.
nginx -T is your friend! You can use this to "test" your configuration for problems before you try to start it.
sudo systemctl restart nginx will attempt to restart nginx, (which as you begin configuration, will likely fail.)
sudo systemctl status nginx.service > ./[path]/log.txt 2>&1 is also your friend. This allows you to collect error messages at runtime that will prevent the service from starting. In my case, the majority of the problems were caused by other services using ports I had selected, or silly mis-configurations.
Once you have nginx started, and the status returns no problems, try sudo netstat -tulpn | grep nginx to make sure it's listening on the correct ports.
Troubleshooting nginx after you have it running:
Most browsers, (Firefox and Chrome at least) support a "developer mode" that you enter by pressing F-12. The console messages can be very helpful.
SSL certificates:
Unlike other SSL servers, nginx requires the site certificate to be combined with the intermediate certificate bundle received from the certificate authority by using cat mycert.crt bundle.file > combined.crt to create it.
Ultimately I ended up with the following configuration file:
Note that I commented out the HTTP redirect as there was a service using port 80 on my device. Under normal conditions, you will want to automatically re-direct port 80 to the secure connection.
Also note that I did not use hard-coded IP addresses in the config file. This allows you to reconfigure the target IP address if necessary.
A corollary to that is - if you're redirecting to an internal secure device configured with the same certificates, you have to pass it through as the domain instead of the IP address, otherwise the secure connection will fail.
#server {
# listen example.com:80;
# server_name example.com;
# return 301 https://example.com$request_uri;
# }
# This is the "web" server (command and control), running Flask/Werkzeug
# that must be passed through as a secure connection so that the
# joystick/gamepad works.
#
# Note that the internal Flask server must be configured to use a
# secure connection too. (Actually, that may not be true, but that's
# how I set it up. . .)
#
server {
listen example.com:443 ssl;
server_name example.com;
ssl_certificate /usr/local/share/ca-certificates/extra/combined.crt;
ssl_certificate_key /usr/local/share/ca-certificates/extra/example.com.key;
ssl_prefer_server_ciphers on;
location / {
proxy_pass https://example.com:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# This is the video streaming port/server running streamserver
# which is not, and cannot be, secured. However, since most
# modern browsers will not mix insecure and secure content on
# the same page, the outward facing connection must be secure.
#
server {
listen example.com:5001 ssl;
server_name example.com;
ssl_certificate /usr/local/share/ca-certificates/extra/combined.crt;
ssl_certificate_key /usr/local/share/ca-certificates/extra/www.example.com.key;
ssl_prefer_server_ciphers on;
# After securing the outward facing connection, pass it through
# as an insecure connection so streamserver doesn't barf.
location / {
proxy_pass http://example.com:5002;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Hopefully this will help the next person who encounters this problem.
We are using nginx for load balancing our application. There are 4 nodes which need to be load-balanced in round-robin fashion.
The load balancing is working fine.
The runtime service is listening at port 9001, which internally redirects to other service on the same node.
So we have defined upstream in nginx.conf, with state file "cluster.state". Following is the excerpt from nginx.conf
upstream cluster {
zone cluster 64k;
state /var/nginx/state/cluster.state;
}
Following is the excerpt in "server" block to route the calls:
location /apipattern {
proxy_set_header Host $host:$server_port;
proxy_read_timeout 300s;
proxy_pass http://cluster/;
}
Following is the excerpt from cluster.state file (changed FQDNs, but port is correct)
server foobar1.com:9001 resolve;
server foobar2.com:9001 resolve;
server foobar3.com:9001 resolve;
server foobar4.com:9001 resolve;
The requirement is to put a healtcheck in place (for nodes mentioned in cluster.state).
The healthcheck services (2 services), on these nodes are available on port 8081 and 8082, with uri=/healthcheck/isup (and NOT on 9001)
How do we configure these healthchecks?
you can add multiple health_check directives under location directive with custom port and uri to have compound/multiple monitors for upstream. This is only possible with NGINX plus, which offers active health checking.
location /juice {
proxy_set_header Host $host;
proxy_pass http://juice/;
health_check port=800 uri=/custom.html;
health_check port=8081 uri=/hello.html;
}
Previously, I had a single staging environment reachable behind a DNS staging.example.com/. Behind this address is a nginx proxy with the following config. Note that my proxy either redirects
To a (s3 behind) cloudfront distribution (app1)
To a loadbalancer by forwarding the host name (and let's consider my ALB is able to pick the appropriate app based on the host name) (app2)
server {
listen 80;
listen 443 ssl;
server_name
staging.example.com
;
location / {
try_files /maintenance.html #app1;
}
location ~ /(faq|about_us|terms|press|...) {
try_files /maintenance.html #app2;
}
[...] # Lots of similar config than redirects either to app1 or app2
# Application hosted on s3 + CloudFront
location #app1 {
proxy_set_header Host app1-staging.example.com;
proxy_pass http://d2c72vkj8qy1kv.cloudfront.net;
}
# Application hosted behind a load balancer
location #app2 {
proxy_set_header Host app2-staging.example.internal;
proxy_set_header X-ALB-Host $http_host;
proxy_pass https://staging.example.internal;
}
}
Now, my team needs a couple more staging environments. We are not yet ready to transition to docker deployments (the ultimate goal of being able to spawn a complete infra per branch that we need to test... is a bit overkill given our team size) and I'm trying to pull out some tricks instead so we can easily get a couple more staging environments using roughly the same nginx config.
Assume I have a created a few more DNS names with a index_i like staging1.example.com, staging2.example.com. So my nginx proxy will receive requests with a host header that looks like staging#{index_i}.example.com
What I'm thinking of doing :
For my s3 + Cloudfront app, I'm thinking of nesting my files under [bucket_id]/#{index_i}/[app1_files] (previously they were directly in the root folder [bucket_id]/[app1_files])
For my load balancer app, let's assume my load balancer knows where to dispatch https://staging#{iindex_i}.example.com requests.
I'm trying to pull something like this
# incoming host : staging{index_i}.example.com`
server {
listen 80;
listen 443 ssl;
server_name
staging.example.com
staging1.example.com
staging2.example.com # I can list them manually, but is it possible to have something like `staging*.example.com` ?
;
[...]
location #app1 {
proxy_set_header Host app1-staging$index_i.example.com; # Note the extra index_i here
proxy_pass http://d2c72vkj8qy1kv.cloudfront.net/$index_i; # Here proxy_passing to a subfolder named index_i
}
location #app2 {
proxy_set_header Host app2-staging$index_i.example.internal; # Note the extra index_i here
proxy_set_header X-ALB-Host $http_host;
proxy_pass http://staging$index_i.example.internal; # Here I am just forwarding the host header basically
}
So ultimately my questions are
- When my nginx server receives a connexion, can I extract the index_i variable from the request host header (using maybe some regex ?)
- If yes, how can effectively implement the app1 and app2 blocks with index_i ?
After looking at several other questions, I was able to come up with this config that works perfectly: it's possible to extract the said variable using a regex in the host name.
On the downside, for my static single page applications, to make it work with S3, I had to create one bucket per "staging index" (because of the way static hosting on S3 works with website hosting / a single index.html to be used on 404). This made it in turn impossible to work with a single Cloudfront distribution in front of my (previously single) s3.
Here is example of using a proxy with a create-react-app frontend and server-side rendering behind an ALB
server {
listen 80;
listen 443 ssl;
server_name ~^staging(?<staging_index>\d*).myjobglasses.com$
location #create-react-app-frontend {
proxy_pass http://staging$staging_index.example.com.s3-website.eu-central-1.amazonaws.com;
}
location #server-side-rendering-app {
# Now Amazon Application Load Balancer can redirect traffic based on ANY HTTP header
proxy_set_header EXAMPLE-APP old-frontend;
proxy_pass https://staging$staging_index.myjobglasses.com;
}
Through Jelastic's dashboard, I created this:
I just clicked "New environment", then I selected nodejs. I added a docker image (of mailhog).
Now, I would like that port 80 of my environment serves the nodejs application. This is by default so. Therefore nothing to do.
In addition to this, I would like port 8080 (or any other port than 80, like port 5000 for example) of my environment serves mailhog, hosted on the docker image. To do that, I added the following lines to the nginx-jelastic.conf (right after the first server serving the nodejs app):
server {
listen *:8080;
listen [::]:8080;
server_name _;
location / {
proxy_pass http://mailhog_upstream;
}
}
where I have also defined mailhog_upstream like this:
upstream mailhog_upstream{
server 10.102.8.215; ### DEFUPPROTO for common ###
sticky path=/; keepalive 100;
}
If I now browse my environment's 8080 port, then I see ... the nodejs app. If I try any other port than 80 or 8080, I see nothing. Putting another server_name doesn't help. I tried several things but nothing seems to work. Why is that? What am I doing wrong here?
Then I tried to get rid of the above mailhog_upstream and instead write
server {
listen *:5000;
listen [::]:5000;
server_name _;
location / {
proxy_pass http://10.102.8.215;
}
}
Browsing the environment's port 5000 doesn't work either.
If I replace the IP of the nodejs' app with that of my mailhog service, then mailhog runs on port 80. I don't understand how I can make the nodejs app run on port 80 and the mailhog service on port 5000 (or any other port than 80).
Could someone enlighten me please?
After all those failures, I tried another ansatz. Assume the path my env is example.com/. What I've tried above is to get mailhog to work upon calling example.com:5000, which I failed doing. Then I tried to make mailhog available through a call to example.com/mailhog. In order to do that, I got rid of all my modifications above and completed the current server in nginx-jelastic.conf with
location /mailhog {
proxy_pass http://10.102.8.96:8025/;
add_header Set-Cookie "SRVGROUP=$group; path=/";
}
That works in the sense that if I know browse example.com/mailhog, then I get something on the page, but not exactly what I want: it's the mailhog's page without any styling. Also, when I call mailhog's API through example.com/mailhog/api/v2/messages, I get a successful response without body, when I should've received
{"total":0,"count":0,"start":0,"items":[]}
What am I doing wrong this time?
Edit
To be more explicit, I put the following manifest that exhibits the second problem with the nginx location.
Full locations list for your case is a following:
(please pay attention to URIs in upstreams, they are different)
location /mailhog { proxy_pass http://172.25.2.128:8025/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection " upgrade"; }
location /mailhog/api { proxy_pass http://172.25.2.128:8025/api; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection " upgrade"; }
location /css { proxy_pass http://172.25.2.128:8025; }
location /js { proxy_pass http://172.25.2.128:8025; }
location /images { proxy_pass http://172.25.2.128:8025; }
that works for me with your application
# curl 172.25.2.127/mailhog/api/v2/messages
{"total":0,"count":0,"start":0,"items":[]}
The following ports are opened by default: 80, 8080, 8686, 8443, 4848, 4949, 7979.
Additional ports can be opened using:
endpoints - maps the container internal port to random external
via Jelastic Shared LB
Public IP - provides a direct access to all ports of your
container
Read more in the following article: "Container configuration - Ports". This one may also be useful:"Public IP vs Shared Load Balancer"
My title isn't the best, my knowledge of webstuff is quite basic, sorry.
What I want to achieve
I have one box fanbox running nginx on Archlinux that I use as main entry point to my home LAN from the internet (namely work where I can only get out to port 80 and 443) via the reverse proxy facility using a changing domain name over which I have no control and that we will call home.net for now.
fanbox has its ports 80 and 443 mapped to home.net, that part was easy.
I have 2 webservers behind the firewall, web1.lan, web2.lan, web2ilo.lan. Both of these have various applications (which may have same name on different machines) that can directly be accessed on the LAN via standard URLs (the names are given as examples, I have no control over the content):
http://web1.lan/phpAdmin/
http://web1.lan/gallery/
http://web2.lan/phpAdmin/
http://web2.lan/dlna/
...and so on...
Now web2ilo.lan is a particular case. It's the out of band management web interface of the HP server web2.lan. That particular webserver offers only 1 application, so it can only be accessed via its root URL:
http://web2ilo/login.html
My goal is to access these via subpath of home.net like this:
http://home.net/web1/phpAdmin/
http://home.net/web1/gallery/
http://home.net/web2/phpAdmin/
http://home.net/web2/dlna/
http://home.net/web2ilo/login.html
My problem
That nearly works but the web applications tend to rewrite URLs so that after I login to, respectively:
http://home.net/web1/phpAdmin/login.php
http://home.net/web2ilo/login.html
the browser is redirected respectively to
http://home.net/phpAdmin/index.php
http://home.net/index.html
Note that the relative subpaths web1 and web2ilo have gone, which logically give me a 404.
My config
So far, I've searched a lot and I tried many options in nginx without understanding too much what I was doing. Here is my config that reproduces this problem. I've left SSL out for clarity.
server {
listen 443 ssl;
server_name localhost;
# SSL stuff left out for clarity
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /web1/ {
proxy_set_header Host $host;
proxy_redirect off;
proxy_pass https://web1.lan/;
}
location /web2/ {
proxy_set_header Host $host;
proxy_redirect off;
proxy_pass https://web2.lan/;
}
location /web2ilo/ {
proxy_set_header Host $host;
proxy_redirect off;
proxy_pass https://web2ilo.lan/;
}
}
After first answers
After the first couple of answers (thanks!), I realise that my setup is far from common and that I may be heading for trouble all alone.
What would then be a better idea to access the webserver behind the firewall without touching frontend ports and domain/hostname ?
You may wish to consider the use of setting proxy_redirect to let nginx know that it should modify the "backend" server response headers (Location and Refresh) to the appropriate front-end URLs. You can either use the default setting to allow nginx to calculate the appropriate values from your location and proxy_pass directives, or explicitly specify the mappings like below:
proxy_redirect http://web1.lan/ /web1/
See: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_redirect
Note: This only affects response headers - not any links in HTML content, or any Javascript.
If you experience problems with links in content or Javascript, you can either modify the content on the backend servers (which you've indicated may not be possible), or adjust your proxy solution such that front end paths are the same as the back end ones (e.g., rather than http://frontend/web1/phpAdmin you simply have http://frontend/phpAdmin). This would entail adding location directives for each application, e.g.,
location /phpAdmin/ {
proxy_set_header Host $host;
proxy_redirect off;
proxy_pass https://web1.lan/phpAdmin/;
}
Here is a tested example.
In my docker-compose.yml I use the demo image whoami to test:
whoareyou:
image: "containous/whoami"
restart: unless-stopped
networks:
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoareyou.rule=Path(`/whoareyou`)"
- "traefik.http.routers.whoareyou.entrypoints=web"
- "traefik.http.routers.whoareyou.middlewares=https-redirect#file"
- "traefik.http.routers.whoareyou-secure.rule=Path(`/whoareyou`)"
- "traefik.http.routers.whoareyou-secure.entrypoints=web-secure"
- "traefik.http.routers.whoareyou-secure.tls=true"
In my config.yaml I have my https redirect:
http:
middlewares:
https-redirect:
redirectScheme:
scheme: https
I did not find an answer to my question and decided to try a different approach:
I now have containerized most of my servers
I use Traefik (https://containo.us/traefik/) as my "fanbox" (= reverse proxy) as it covers my needs but also solves in a quite easy fashion the SSL certificates stuff.
Thanks all for your suggestions.