ipv6 results in 502, ipv4 doesn't.

May 11, 2017 187 views
Django Nginx One-Click Install Apps Ubuntu 16.04

I have recently set up a new droplet using the Django One-Click Application Image. During installation I enabled ipv6, but now I can't get it working: Requests using ipv6 result in a 502, and for the past hours I was unable to figure out why.

I have setup nginx to listen for ipv6 requests:

upstream appserver {
# server 127.0.0.1:9000 fail
timeout=0;
server unix:/home/django/gunicorn.socket fail_timeout=0;
}

server {
listen 80 defaultserver;
listen [::]:80 default
server ipv6only=on;
server_name myaddress.net www.myaddress.net;

return 301 https://$server_name$request_uri;

}

server {
listen 443 ssl http2 defaultserver;
listen [::]:443 ssl http2 ipv6only=on;
server
name myaddress.net www.myaddress.net;

(...)

location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
        proxy_buffering off;

        proxy_pass http://app_server;
}

}

But requests using ipv6 result in the following error:

2017/05/11 04:05:07 [error] 31425#31425: *8 upstream prematurely closed connection while reading response header from upstream, client: (requesting ipv6), server:(servername), request: "GET / HTTP/2.0", upstream: "http://unix:/home/django/gunicorn.socket:/", host: "(droplet ipv6)"

Requests directly to https://(ipv4-address) result in the "Welcome to Django"-page being displayed.

I have tried to figure this out for the last four hours or so, but at this point I have no idea where I could continue looking.

Any ideas/pointers into some direction?

3 Answers

@antoniocarlosdimeo

From NGINX 1.3.4 and up, the default for ipv6only is set to on, so that part of your configuration isn't needed and can be removed from the server blocks.

NGINX also has an issue with setting ipv6only multiple times which causes it to fail to start, so even if this option was required, you could only set it on one server block per port. The reason it works in the configuration you've posted is because you have it set on two different ports (80 and 443). If you tried to set it on another server block using the same ports, it's cause the issue mentioned.

So:

listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

becomes:

listen 80 default_server;
listen [::]:80 default_server;

and:

listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 ipv6only=on;

becomes:

listen 443 ssl http2 default_server;
listen [::]:443 ssl http2;

That said, additionally, you shouldn't need the ssl directive in the second server block as HTTP/2 is SSL only. So the second server block should actually be:

listen 443 http2 default_server;
listen [::]:443 http2;

Unless you're planning on hosting multiple domains on this Droplet, and you need a catch-all of sorts for requests that don't match other domains, you can also remove the default_server directives too.

So all cleaned up, we'd end up with:

listen 80;
listen [::]:80;

and

listen 443 http2;
listen [::]:443 http2;

...

Beyond the above changes, the rest of the configuration appears to be valid.

Was IPv6 enabled on this Droplet when you deployed it or was it enabled after deployment? Do you have logging setup for your Flask app? If so, what's the log file showing?

I used the checkbox "IPv6" during setup.

When I look into the gunicorn errorlogs it shows me the following:

(...)
File "/usr/lib/python2.7/dist-packages/django/template/base.py", line 849, in resolvelookup
current = current()
File "/usr/lib/python2.7/dist-packages/django/http/request.py", line 152, in buildabsoluteuri
host=self.gethost(),
File "/usr/lib/python2.7/dist-packages/django/http/request.py", line 102, in get
host
raise DisallowedHost(msg)
DisallowedHost: Invalid HTTPHOST header: '[myIPv6]'. You may need to add '[myIPv6]' to ALLOWEDHOSTS.

(...)
File "/usr/lib/python2.7/dist-packages/django/template/base.py", line 849, in resolvelookup
current = current()
File "/usr/lib/python2.7/dist-packages/django/http/request.py", line 152, in buildabsoluteuri
host=self.gethost(),
File "/usr/lib/python2.7/dist-packages/django/http/request.py", line 102, in get
host
raise DisallowedHost(msg)
DisallowedHost: Invalid HTTPHOST header: 'www.myaddress.net'. You may need to add u'www.myaddress.net' to ALLOWEDHOSTS.

Depenging on whether or not I request directly via IP or via my address.

This puzzles me, since djangos settings.py file looks like this:

ALLOWED_HOSTS = ['', u'jmyaddress.net',u'www.myaddress.net','[]',]

and AFAIK the '*' entry should allow all connections.

Okay, I got it.

I set the ALLOWED_HOSTS variable correctly, but it was overriden. In the very bottom of the settings.py file the following was added:

Find out what the IP addresses are at run time This is necessary because otherwise Gunicorn will reject the connections

def ipaddresses():
ip
list = []
for interface in netifaces.interfaces():
addrs = netifaces.ifaddresses(interface)
for x in (netifaces.AFINET, netifaces.AFINET6):
if x in addrs:
iplist.append(addrs[x][0]['addr'])
return ip
list

Discover our IP address

ALLOWEDHOSTS = ipaddresses()

When this code executes it changes the ALLOWED_HOSTS variable. Apparently it can only detect the IPv4 address. When commenting out the very last line

Discover our IP address ALLOWEDHOSTS = ipaddresses()

Everything works as intended.

Have another answer? Share your knowledge.