NGINX passing requests to Pylons and relative URLs - nginx

I have NGINX running on port 8080. I have the following setup in my NGINX conf file.
server {
listen 8080;
server_name domain.com;
location / {
#root /usr/share/nginx/html;
#index index.html index.htm;
proxy_pass http://127.0.0.1:80;
proxy_redirect http://127.0.0.1:80 http://domain.com;
}
These rules work correctly as far as I can tell. The only issue I run into is when Pylons gets a request for a relative URL it is using http://127.0.0.1/linkto/something instead of http://domain.com:8080/linkto/something. I believe I am missing something in my Pylons configuration, if you have any advice or need additional information just let me know. Thanks in advance for any assistance on this.

By default, proxy_pass uses the hostname from the directive (127.0.0.1 in your case) as the Host: header for its request. You probably just need to add proxy_set_header Host $http_host; to have it pass through the original Host header to your backend.

Related

Dynamically proxy to subdomain according to path with nginx?

I am attempting to convert all traffic to its "matching" internal equivalent, and proxy the traffic via this new internal URL.
For example,
http://external.com/image/blue.png will map to http://image.internal.com/blue.png
I have followed the guide here to attempt to set this up via nginx, however I am receiving the following error:
nginx: [emerg] unknown "path" variable
Below is my nginx configuration file:
server {
listen 80;
listen [::]:80;
root /var/www/private/$subdomain;
index index.html index.htm index.nginx-debian.html;
server_name external.com;
location ~ ^/(?<subdomain>[^/]+)/(<path>.*)?$ {
proxy_pass http://$subdomain.internal.com/$path;
proxy_buffering off;
proxy_set_header Host $http_host;
}
}
Do I need to define the 'path' variable somewhere else? All help is appreciated, thank you.
You're missing a question mark. It's ?<path> not <path>

Routing meant for a subdomain is also being applied to the root domain

Consider two websites hosted on the same server: domain.com and foo.domain.com. I want to start up a monitoring panel for each site on port 5555. Each site has a separate monitoring panel so I need to use nginx to route domain.com:5555 and foo.domain.com:5555 to two different places.
Here is the configuration for foo.domain.com:
server {
listen 5555;
server_name foo.domain.com;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://localhost:5678;
}
}
While this works fine for foo.domain.com:5555, it is also routing domain.com:5555 to the monitoring panel. This is acting like I had defined server_name domain.com foo.domain.com, but clearly I only defined it for foo.domain.com.
The only other nginx configs on the server are for ports 80 and 443. Neither of those configs use any wildcards and explicitly use the full name.
nginx always has a default server - if you do not define a default server, it will use the first server block with a matching listen directive.
If you want to discourage this behaviour, you will need to define a catch-all server for port 5555.
For example:
server {
listen 5555 default_server;
return 444;
}
See this document for more.

nginx upstream subdomain on the same server

I am configuring Nginx load balance with Nginx upstream module, configuration as follow:
upstream load {
server loadapi.example.com;
server loadapi.anotherdomain.com down;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://load;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
server {
listen 80;
server_name loadapi.example.com;
root /disk/projects/load/loadapi;
index index.html index.htm index.shtml index.php;
...
...
error_page 404 /404.html;
}
Notice that the api.example.com and loadapi.example.com are on the same server. loadapi.anotherdomain.com is resolved to another server which provides the same service.
Everything works fine with loadapi.anotherdomain.com, which are on another server.
But when I use the loadapi.example.com as the backend, it seems that Nginx cannot handle it correctly. I can get my service up and running on loadapi.example.com. But it is not working with the upstream.(look like Nginx cannot resolve the subdomain name correctly).
any advice? thx in advance.
nginx uses the Host header to determine which server block to use to process a request.
When the request passes through the proxy_pass http://load; statement, the Host header is set to the value load by default.
To make nginx choose the server block containing the server_name loadapi.example.com; statement, it either needs to be the default_server server, or include the name load in its server_name, or set the Host header using:
proxy_set_header Host loadapi.example.com;
Of course, using upstream for load balancing means that both servers receive the same value for the Host header, and must both respond correctly to it.
See this document for more.

Nginx/Pyramid custom SSL port

As a prefix, I have been using the following stack for some time with great success:
NGINX - web proxy
SSL - configured in nginx
Pyramid web application, served by gunicorn
The above combo works great, here is a working configuration.
server {
# listen on port 80
listen 80;
server_name portalapi.example.com;
# Forward all traffic to SSL
return 301 https://www.portalapi.example.com$request_uri;
}
server {
# listen on port 80
listen 80;
server_name www.portalapi.example.com;
# Forward all traffic to SSL
return 301 https://www.portalapi.example.com$request_uri;
}
#ssl server
server {
listen 443 ssl;
ssl on;
ssl_certificate /usr/local/etc/letsencrypt/live/portalapi.example.com/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/portalapi.example.com/privkey.pem;
server_name www.portalapi.example.com;
client_max_body_size 10M;
client_body_buffer_size 128k;
location ~ /.well-known/acme-challenge/ {
root /usr/local/www/nginx/portalapi;
allow all;
}
location / {
proxy_set_header Host $host;
proxy_pass http://10.1.1.16:8005;
#proxy_intercept_errors on;
allow all;
}
error_page 404 500 502 503 504 /index.html;
location = / {
root /home/luke/ecom2/dist;
}
}
Now, this is how I serve my public facing apps, it works very well. For all my internal applications, I used to simply direct users to an internal domain example: http://subdomain.company.domain , again this worked well for a long time.
Now in the wake of KRACK attack although we have some very thorough firewall rules to prevent a lot of attacks, I want to force all internal traffic through SSL, and I don't want to use a self signed certificate, I want to use lets encrypt so I can auto-renew certificates which makes administration much easier (and cheaper).
In order to use lets encrypt, I need to have a public facing DNS and server to perform the ACME challenge (for auto renewing). Now again this was a very easy thing to setup in nginx, and the below config works perfectly for serving static content:
What it does is if a user from the internet accesses intranet.example.com it simply shows a forbidden message. However, if a local user tries, they get forwarded to intranet.example.com:8002 and the port 8002 is only available locally, so there is no way external users can access a webpage on this site
geo $local_user {
192.168.155.0/24 0;
172.16.10.0/28 1;
172.16.155.0/24 1;
}
server {
listen 80;
server_name intranet.example.com;
client_max_body_size 4M;
client_body_buffer_size 128k;
# Space for lets encrypt to perform challenges
location ~ /\.well-known/ {
root /usr/local/www/nginx/intranet;
}
if ($local_user) {
# If user is local, redirect them to SSL proxy only available locally
return 301 https://intranet.example.com:8002$request_uri;
}
# Default block all non local users see
location / {
root /home/luke/forbidden_html;
index index.html;
}
# This server block is only available to local users inside geo $local_user
# this block listens on an internal port only, so it is never availble to
# external networks
server {
listen 8002 default ssl; # listen on a port only accessible locally
server_name intranet.example.com;
ssl_certificate /usr/local/etc/letsencrypt/live/intranet.example.com/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/intranet.example.com/privkey.pem;
client_max_body_size 4M;
client_body_buffer_size 128k;
location / {
allow 192.168.155.0/24;
allow 172.16.10.0/28; # also add in allow/deny rules in this block (extra security)
allow 172.16.155.0/24;
root /home/luke/ecom2/dist;
index index.html;
deny all;
}
}
Now, here comes the pyramid/nginx marrying problem, if I use the same above configuration, but have the below settings for my server on 8002:
server {
listen 8002 default ssl; # listen on a port only accessible locally
server_name intranet.example.com;
ssl_certificate /usr/local/etc/letsencrypt/live/intranet.example.com/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/intranet.example.com/privkey.pem;
client_max_body_size 4M;
client_body_buffer_size 128k;
location / {
allow 192.168.155.0/24;
allow 172.16.10.0/28; # also add in allow/deny rules in this block (extra security)
allow 172.16.155.0/24;
# Forward all requests to python application server
proxy_set_header Host $host;
proxy_pass http://10.1.1.16:6543;
proxy_intercept_errors on;
deny all;
}
}
I run into all sorts of problems, first off inside pyramid I was using the following code in my views/templates
request.route_url # get route url for desired function
Now using request.route_url with the above settings should cause https://intranet.example.com:8002/login to route tohttps://intranet.example.com:8002/welcome but in reality, this setup would forward a user to: http://intranet.example.com/welcome Again this is not correct.
And if I use route_url with the NGINX proxy setting:
proxy_set_header Host $http_host;
I get the error: NGINX to return a 400 error:
400: The plain HTTP request was sent to HTTPS port
And a request to: https://intranet.example.com:8002/ gets reverted to: http://intranet.example.com/login (omitting port and https)
Then I used the same nginx settings (header $htto), but thought I would change to using:
request.route_path
My theory was this should force everything to stay on the same url prefix, and just forward a user from https://intranet.example.com:8002/login to https://intranet.example.com:8002/welcome but in reality, this setup performed the same way as using route_url.
proxy_set_header Host $http_host;
I then get an error when navigating to https://intranet.example.com:8002
400: The plain HTTP request was sent to HTTPS port
And a request to: https://intranet.example.com:8002/ gets reverted to: http://intranet.example.com/login (omitting port and https)
Can anyone assist with the correct setup in order for me to serve my application on https://intranet.example.com:8002
EDIT:
Have also tried:
location / {
allow 192.168.155.0/24;
allow 172.16.10.0/28; # also add in allow/deny rules in this block (extra security)
allow 172.16.155.0/24;
# Forward all requests to python application server
proxy_set_header Host $host:$server_port;
proxy_pass http://10.1.1.16:8002;
proxy_intercept_errors on;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# root /home/luke/ecom2/dist;
# index index.html;
deny all;
}
Which gives the same result.
I’ve checked a similar configuration and your last example seems correct,
at least for a simplistic gunicorn/pyramid app combination.
Seems something is missing in your puzzle )
Here’s my code (I’m new to Pyramid so something might be done better)
helloworld.py
from pyramid.config import Configurator
from pyramid.renderers import render_to_response
def main(request):
return render_to_response('templates:test.pt', {}, request=request)
with Configurator() as config:
config.add_route('main', '/')
config.add_view(main, route_name='main')
config.include('pyramid_chameleon')
app = config.make_wsgi_app()
templates/test.pt
<html>
<body>
Route url: ${request.route_url('main')}
</body>
</html>
My nginx config
server {
listen 80;
server_name pyramid.lan;
location / {
return 301 https://$server_name:8002$request_uri;
}
}
server {
listen 8002;
server_name pyramid.lan;
ssl on;
ssl_certificate /usr/local/etc/nginx/cert/server.crt;
ssl_certificate_key /usr/local/etc/nginx/cert/server.key;
location / {
proxy_set_header Host $host:$server_port;
proxy_pass http://127.0.0.1:5678;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
This is how I run gunicorn:
gunicorn -w 1 -b 127.0.0.1:5678 helloworld:app
And yes, it works:
$ curl --insecure https://pyramid.lan:8002/
<html>
<body>
Route url: https://pyramid.lan:8002/
</body>
</html>
$ curl -D - http://pyramid.lan
HTTP/1.1 301 Moved Permanently
Server: nginx/1.12.2
Date: Thu, 02 Nov 2017 20:41:50 GMT
Content-Type: text/html
Content-Length: 185
Connection: keep-alive
Location: https://pyramid.lan:8002/
Lets figure out what might go wrong in your case
http 400 usually pops up when you go over httP instead of httpS to a server awaiting httpS requests. If there’s no typo in the post and it indeed occurs when you navigate to https://intranet.example.com:8002 it would be nice to see a curl request showing this and a tcpdump showing what’s happening. Actually you can easily reproduce it by simply typing http://intranet.example.com:8002
another idea is that you’re doing a redirect from your app and the link gets broken when the redirect occurs. I better description on how the user may navigate from https://intranet.example.com:8002/login to .../welcome would be helpful
one more idea is that your app is not that simple and you use some middlewares / customization that makes the default logic work differently and your X-Forwarded-Proto header gets ignored - in this case the behavior would be just as you described
The issue here is, obviously, the missing port within the Location directives that your backend produces.
Now, why is the port missing? Most certainly, because of the following code:
proxy_set_header Host $host;
Note that $host itself does not contain $server_port, unlike $http_host, so, your backend would have no way of knowing which port you meant if you just use $host all by itself.
Note that proxy_redirect default of default expects Location to correspond with the value from proxy_pass in order to do its magic (according to documentation), so, your explicit header setting likely interferes with such logic.
As such, from the nginx point of view, I see multiple possible independent solutions:
remove proxy_set_header Host, and let proxy_redirect do its magic;
set proxy_set_header Host appropriately, to include the port number, e.g., using $host:$server_port or $http_host as you see fit (if that doesn't work, then perhaps the deficiency is actually within your upstream app itself, but fear not -- read below);
provide a custom proxy_redirect setting, e.g., proxy_redirect https://pyramid.lan/ / (equivalent to proxy_redirect https://pyramid.lan/ https://pyramid.lan:8002/), which will ensure that all the Location responses will have the proper port; the only way this wouldn't work is if your upstream does non-HTTP redirects with the missing port.

Nginx: How to forward requests to a port using proxy_pass

I'm just getting started with Nginx and am trying to set up a server block to forward all requests on the subdomain api.mydomain.com to port 8080.
Here's what I've got:
UPDATED:
server {
server_name api.mydomain.com;
location / {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-for $remote_addr;
}
}
server {
server_name www.mydomain.com;
return 301 $scheme://mydomain.com$request_uri;
}
server {
server_name mydomain.com;
root /var/www/mydomain.com;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
The server block exists in /etc/nginx/sites-available and I have created a symlink in /etc/nginx/sites-enabled.
What I expect:
I'm running deployd on port 8080. When I go to api.mydomain.com/users I expect to get a JSON response from the deployd API, but I get no response instead.
Also, my 301 redirect for www.mydomain.com is not working. That block was code I copied from Nginx Pitfalls.
What I've tried:
Confirmed that mydomain.com:8080/users and $ curl
http://127.0.0.1:8080/users return the expected response.
Restarted the nginx service after making changes to the server block.
Tried removing the proxy_set_header lines.
Any idea what I'm missing here?
You shouldn't need to explicitly capture the URL for your use case. The following should work for your location block:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}
As it turns out, my problem was not with Nginx configuration but rather with my DNS settings. I had to create an A NAME record for each of my sub-domains (www and api). Rookie mistake.
A colleague of mine actually helped me troubleshoot the issue. We discovered the problem when using telnet from my local machine to connect to the server via IP address and saw that Nginx was, in fact, doing what I intended.

Resources