Rewriting URL in an nginx Docker container wrongly uses internal port - django

I have a Django application that runs in a Docker environment; one container for gunicorn and one for nginx. My application's nginx server listens on port 9081, which is internal to the system (it's not exposed to the outside world). Another nginx container (which routes traffic) sits on port 80 and sends traffic to my site as necessary (based on the hostname a request receives).
Here's my application's nginx setup, stripped down to the basics:
upstream project {
server gun_project:8001; # gunicorn container
}
server {
listen 9081;
server_name mytool.myhost.com;
set_real_ip_from 172.17.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
location / {
proxy_pass http://project;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
Here's the router nginx setup, again stripped down:
upstream project {
server ngx_project:9081; # nginx container
}
server {
listen 80;
server_name mytool.myhost.com;
return 302 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name mytool.myhost.com;
# SSL Info
ssl_certificate /etc/nginx/ssl/mycert.cer;
ssl_certificate_key /etc/nginx/ssl/mycert.key;
location / {
proxy_pass http://project;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
I want to redirect a URL on this site from one location to another (the URL has permanently changed). I'm doing so via a rewrite in the location block of my application's nginx configuration (the first nginx block above):
location / {
rewrite "^/oldpath/$" /newpath/ permanent;
proxy_pass http://project;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
When I do this, and I attempt to load the old URL (mytool.myhost.com/oldpath/) in a web browser, I'm redirected to mytool.myhost.com:9081/newpath/ which fails because it doesn't exist (that port isn't exposed externally).
Is there something basic I'm missing? I don't want that internal port to be a part of the redirect.

Here's how I ended up doing it:
I added a dedicated location in the nginx configuration for the site, and performed the redirect there:
# Redirect the previous URL to the newer one
location = /old-path/ {
return 302 https://$host/new-path/;
}

Related

docker + nginx http requests not working in browsers

I have a AWS EC2 instance running Linux with docker containers running gunicorn/django and an nginx reverse proxy.
I don't want it to redirect to https at the moment.
When I try to reach the url by typing out http://url.com in the browser it seems to automatically change to https://url.com and gives me ERR_CONNECTION_REFUSED. The request doesn't show up at all in the nginx access_log.
But when I try to reach it with curl I get a normal response and it does show up in the nginx access_log.
I have ascertained that the django security middleware is not the cause as the HSTS options are disabled.
I've tried clearing the browser cache and deleting the domain from the chrome security policies.
nginx config:
upstream django_server {
server app:8001 fail_timeout=0;
}
server {
listen 80;
server_name url.com www.url.com;
client_max_body_size 4G;
charset utf-8;
keepalive_timeout 5;
location /static/ {
root /usr/share/nginx/sdev/;
expires 30d;
}
location / {
proxy_redirect off;
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-Host $server_name;
proxy_pass http://django_server;
}
}
}
What am I overlooking?

Why does the query service in my Wikibase installed through Docker not contain content added to the main wikibase store?

We've installed wikibase-docker with an Apache server in front of it to handle SSL and proxy two vhosts to the ports from Docker.
api.example.com (to http://127.0.0.1:8181)
query.example.com (to http://127.0.01:8282/)
In the log from the wdqs-updater I see:
org.wikidata.query.rdf.tool.rdf.Munger$BadSubjectException: Unrecognized subjects: [https://api.example.com/entity/statement/Q12-caba1d44-46d5-8598-9185-784a75e4cebb, https://api.example.com/entity/statement/Q12-4c77991e-4674-5301-75f1-5b494612b56b, https://api.example.com/wiki/Special:EntityData/Q12, https://api.example.com/entity/Q12].
Expected only sitelinks and subjects starting with http://wikibase.svc/wiki/Special:EntityData/ and [http://wikibase.svc/entity/]
The 'wikibase.svc' name is used in the docker-compose.yml file and is the internal docker name.
To get the MediaWiki search working I had to update ${DOLLAR}wgServer = WebRequest::detectServer() in LocalSettings.php.template to have the value "https://api.example.com"
What do I need to change to make it work? All references to wikibase.svc in the docker-compose.yml file? Or something else?
I already tried updating WIKIBASE_HOST= for the wdqs-updater container, but that didn't seem to help.
In docker-compose you have a list of variables that work perfectly on a localhost. When you need to deploy it in production you need to change few variables to define the public hostname, Ip and SSL. I did setup a nginx setup to manage the hostname and SSL certificate.
In my setup I have 1 hostname per service, my Nginx that forward the request to the right port number all the wikibase on the same machine.
My query service setting for nginx add the ssl certificate and forward to the port 8282 the request sent to https://query.example.com
On my production machine I "just" need to replaxe example.com by personaldata.io.
server {
listen 80;
server_name query.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443;
server_name query.example.com;
ssl on;
ssl_certificate /etc/letsencrypt/live/query.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/query.example.com/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/query.example.com.log;
location / {
proxy_pass http://127.0.0.1:8282;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-forwarded-host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
The variables I had to change on my setup:
QS_PUBLIC_SCHEME_HOST_AND_PORT=https://qs.example.com:443 # Public domain name and port
WIKIBASE_SCHEME=https
WIKIBASE_HOST=wiki.example.com
QS_PUBLIC_SCHEME_HOST_AND_PORT=https://qs.example.com:443
WB_PUBLIC_SCHEME_HOST_AND_PORT=https://wiki.example.com:443
WIKIBASE_SCHEME_AND_HOST=https://wiki.example.com

How to to forbid access my site from ip address+port using nginx?

I have a django app with gunicorn running on port 2333.In nginx.conf I set
server {
listen 80;
server_name mydomain.com;
location / {
proxy_cache my_cache;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:2333;
expires 30d;
}
now I can view my django app on address http://ipaddress:2333 and mydomain.com
but I don't want users to view my site by http://ipaddress:2333 .How to allow nginx only use mydomain.com to access my site.
I have tried to use "server default".It not worked.
server {
listen 2333 default;
server_name _;
return 500;
}
Nginx has nothing to do with that. Your Gunicorn (Django) app is listening on port 2333. Therefore, you can bypass nginx by connecting to http://$SERVER:2333. It will work even if you stop nginx.
What you need to do is tell gunicorn to listen only on the localhost, e.g. with --bind=127.0.0.1:2333. Then port 2333 will be accepting connections only from the local network interface.

Rails 4 + Websocket-rails + Passenger + Nginx + Load balancer

I've added some features to a couple of our web apps that needs websocket-rails. Everything works fine in development, but I am not sure how to deploy all this in our production environment since it's a bit more complex.
The production setup:
1 server used as a Load balancer (Nginx).
2 servers used as web servers, where our rails apps run using Nginx and Passenger (both servers are identical).
Several other servers used by the app servers but I believe they are irrelevant for this question.
All sites are running on HTTPS.
Load balancer configs
Here's an example for one of the sites, the others have similar configs:
upstream example {
ip_hash;
server xx.xx.xx.xx:443;
server xx.xx.xx.xx:443;
}
server {
listen 80;
listen 443 ssl;
ssl on;
ssl_certificate /etc/nginx/ssl/example.chained.crt;
ssl_certificate_key /etc/nginx/ssl/example.key;
server_name example.com;
rewrite ^(.*) https://www.example.com$1 permanent;
}
server {
listen 80;
listen 443 ssl;
ssl on;
ssl_certificate /etc/nginx/ssl/example.chained.crt;
ssl_certificate_key /etc/nginx/ssl/example.key;
server_name www.example.com;
if ($ssl_protocol = "") {
rewrite ^ https://$server_name$request_uri? permanent;
}
client_max_body_size 2000M;
location /css { root /home/myuser/maintenance; }
location /js { root /home/myuser/maintenance; }
location /img { root /home/myuser/maintenance; }
location /fonts { root /home/myuser/maintenance; }
error_page 502 503 #maintenance;
location #maintenance {
root /home/myuser;
if ($uri !~ ^/maintenance/) {
rewrite ^(.*)$ /maintenance/example.html break;
}
}
location / {
proxy_pass https://example;
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;
}
}
Web server configs
Again, here's an example for one of the sites, the others have similar configs:
server {
server_name example.com;
rewrite ^(.*) https://www.example.com$1 permanent;
}
server {
listen 80;
listen 443 ssl;
ssl on;
ssl_certificate /etc/nginx/ssl/example.chained.crt;
ssl_certificate_key /etc/nginx/ssl/example.key;
root /var/www/example/public;
server_name www.example.com;
if ($ssl_protocol = "") {
rewrite ^ https://$server_name$request_uri? permanent;
}
client_max_body_size 2000M;
passenger_enabled on;
rails_env production;
passenger_env_var SECRET_KEY_BASE "SOME_SECRET";
}
What I've gathered so far:
I'll need to enable passenger sticky sessions
I'll need to create a location in the site's server section where the websocket server is listening to.
I'll need to override the concurrent requests of passenger for the websocket location to unlimited.
My Questions:
Do I have to enable the passenger sticky sessions also in the load balancer's configs? I am guessing this is only for the web servers.
How would the location section for the websocket server look like?
Do I have to create the websocket location section also on the load balancer?
Having the sticky sessions is enough to keep the various apps and servers in synch?
I have various apps running on each server and they should all receive the same notifications (socket messages) so they should all connect to the same websocket server (I'm guessing). Now that websocket-rails is part of their gemsets, won't each app try to spawn their own websocket server? If so, how do I prevent that and make them spawn only one in case none is running yet?
As you can see I am quite confused about how websocket-rails works with passenger and nginx in production so even if you don't have all the answers, any input is greatly appreciated!
UPDATE
I've tried the following on the load balancer:
upstream websocket {
server xx.xx.xx.xx:443;
server xx.xx.xx.xx:443;
}
location /websocket {
proxy_pass https://websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade websocket;
proxy_set_header Connection Upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#also tried with this:
#proxy_set_header Upgrade $http_upgrade;
#proxy_set_header Connection "upgrade";
}
and on the app servers:
location /websocket {
proxy_pass https://www.example.com/websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade websocket;
proxy_set_header Connection Upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#also tried with this:
#proxy_set_header Upgrade $http_upgrade;
#proxy_set_header Connection "upgrade";
}
On the client side I connect to the url WebSocketRails('www.example.com/websocket'); and i get the following error:
WebSocket connection to 'wss://www.example.com/websocket' failed: Error during WebSocket handshake: Unexpected response code: 404
Any ideas?
I don't think you'll need passenger sticky sessions on the load balancer
This blog covers relevant WebSocket config for NGINX. You need the WebSocket config on the load balancer, and also on the web server if you want to pass the Upgrade and Connection headers to the rails app.

nginx rewrite leads to redirect loop

I'm using nginx with a reverse proxy to my gunicorn django server at local to run my django app.
I am trying to force ssl from any http request, as the site is only intended https access. The app will ONLY listen to port 8888, (80 and 443 taken), so the site should only be accessible when specified the 8888 port.
I tried to rewrite with this rewrite ^ https://domain.net:8888$request_uri? permanent; in the server block and location block. Not only does it not redirect http requests to the same url, it also causes a redirect loop when there is a https request.
server {
listen 8888;
server_name sy-system.net;
rewrite ^ https://domain.net:8888$request_uri? permanent;
ssl on;
ssl_certificate /path/to/domain.pem;
ssl_certificate_key /path/to/domain.key;
# serve directly - analogous for static/staticfiles
location /media/ {
root /path/to/root;
}
location /static/ {
root /path/to/root;
}
location / {
#rewrite ^ https://sy-system.net:8888$request_uri? permanent;
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_connect_timeout 10;
proxy_read_timeout 10;
proxy_pass http://127.0.0.1:8881/;
proxy_set_header X-Forwarded-Protocol https;
}
# what to serve if upstream is not available or crashes
error_page 500 502 503 504 /media/50x.html;
}
You are unconditionally redirecting from domain.net:8888 to domain.net:8888. An infinite redirect loop is the only possible result.
In any case, what you are attempting to do isn't possible. Nginx is talking SSL, your browser is not, so no data will be transferred between them (which is why you don't get the redirect loop when connecting with plain HTTP). When they do talk (via SSL) your redirect loop takes over.