Search code examples
apacheflasksslgunicornmod-proxy

Flask app with Apache proxy + Gunicorn not working on HTTPS


Updates in the bottom, I kind of solved it but not sure if the solution is a correct one.


I have Apache running on CentOS with a proxy to localhost port 8080 where I have Flask app running using Gunicorn. This setup works on Apache port 80 (HTTP) and I can connect to it using my domain http://example.com with a browser but now I have tried to setup SSL/HTTPS and it just doesn't work.

Navigating to https://example.com tries to load the page for a while (like 30ish seconds) and then it shows 502 error page:

Proxy Error
The proxy server received an invalid response from an upstream server.
The proxy server could not handle the request GET /.
Reason: Error reading from remote server

Apache error log:

[proxy_http:error] [pid 30209] (103)Software caused connection abort: [client xx.xxx.xxx.xxx:60556] AH01102: error reading status line from remote server localhost:8080
[proxy:error] [pid 30209] [client xx.xxx.xxx.xxx:60556] AH00898: Error reading from remote server returned by /

Gunicorn error log (first 7 lines are from Gunicorn startup, no idea why this info is in error log, last 3 lines are when the HTTPS request returns 502 error):

[29478] [INFO] Listening at: http://127.0.0.1:8080 (29478)
[29478] [INFO] Using worker: sync
[29480] [INFO] Booting worker with pid: 29480
[29481] [INFO] Booting worker with pid: 29481
[29482] [INFO] Booting worker with pid: 29482
[29483] [INFO] Booting worker with pid: 29483
[29484] [INFO] Booting worker with pid: 29484
[29478] [CRITICAL] WORKER TIMEOUT (pid:29480)
[29480] [INFO] Worker exiting (pid: 29480)
[29554] [INFO] Booting worker with pid: 29554

Apache config which is working for HTTP (/etc/httpd/conf/httpd.conf):

Listen 80

#other default config values here

<VirtualHost *:80>
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
</VirtualHost>

IncludeOptional conf.d/*.conf

Apache config which is not working for HTTPS (/etc/httpd/conf.d/ssl.conf):

Listen 443 https

#other default config values here

<VirtualHost *:443>

#other default config values here too

SSLCertificateFile /etc/pki/tls/certs/cert.pem
SSLCertificateKeyFile /etc/pki/tls/private/cert.key

SSLProxyEngine on
ProxyPass / https://localhost:8080/
ProxyPassReverse / https://localhost:8080/

</VirtualHost>

If I remove/comment the SSLProxyEngine, ProxyPass amd ProxyPassReverse lines and restart Apache I get the default Apache welcome page and HTTPS works just fine so clearly the problem is in the Proxy somehow?

The flask app is started with Gunicorn by:

gunicorn --config gunicorn_config.py app:app

gunicorn_config.py:

workers = 5
bind = '127.0.0.1:8080'
umask = 0o007
reload = True
accesslog = 'log_gunicorn_access.txt'
errorlog = 'log_gunicorn_error.txt'

app.py:

from flask import Flask

app = Flask(__name__)

@app.route('/')
    def hello_world():  
        return 'Hello world!'

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8080)

And once again, this works when navigating to my domain using HTTP but doesn't work when using HTTPS.

Any help?


UPDATE:

I managed to get another error. Now navigating to https://example.com loads instantly and shows 500 error page:

Proxy Error
The proxy server could not handle the request GET /.
Reason: Error during SSL Handshake with remote server

Apache error log:

[proxy:error] [pid 32385] (502)Unknown error 502: [client xx.xxx.xxx.xxx:50932] AH01084: pass request body failed to [::1]:8080 (localhost)
[proxy:error] [pid 32385] [client xx.xxx.xxx.xxx:50932] AH00898: Error during SSL Handshake with remote server returned by /
[proxy_http:error] [pid 32385] [client xx.xxx.xxx.xxx:50932] AH01097: pass request body failed to [::1]:8080 (localhost) from xx.xxx.xxx.xxx ()

No more any errors in Gunicorn error log.

I added these two lines to gunicorn_config.py:

keyfile = '/etc/pki/tls/private/cert.key'
certfile = '/etc/pki/tls/certs/cert.pem'

and made sure both files are accessible by the user running Gunicorn (chmod o+r cert.key/pem).

No idea if I was supposed to change it like this as I thought the traffic should go like: client --https--> Apache and then Apache --http--> Gunicorn.

Also HTTP (http://example.com) no longer works and gives the previous 502 error page but I guess running Gunicorn with the cert configs doesn't allow HTTP anymore and would need to run the app twice with different configs).


UPDATE 2:

I added more Apache logging by adding this line to /etc/httpd/conf.d/ssl.conf inside virtual host:

LogLevel info

And now I got additional info in Apache error log:

[ssl:info] [pid 3808] [remote 127.0.0.1:8080] AH02411: SSL Proxy: Peer certificate does not match for hostname localhost

Then I added new line to /etc/httpd/conf.d/ssl.conf inside virtual host:

SSLProxyCheckPeerName off

And now I got another Apache error:

[ssl:info] [pid 3999] [remote 127.0.0.1:8080] AH02005: SSL Proxy: Peer certificate CN mismatch: Certificate CN: example.com Requested hostname: localhost

Added new line to /etc/httpd/conf.d/ssl.conf inside virtual host:

SSLProxyCheckPeerCN off

Aaaand now navigating to https://example.com correctly works and I get "Hello world" back from the app!

Now I guess my question needs update as well: Is it bad practice, wrong or insecure to use SSLProxyCheckPeerName off and SSLProxyCheckPeerCN off in this context? Or is there a better way as I don't think there's a way to order an official SSL certificate on localhost?


Solution

  • You're using

    ProxyPass / http://localhost:8080/
    

    and

    ProxyPass / https://localhost:8080/
    

    (note the 1 letter difference).

    Your localhost:8080 will serve either http or https. Based on your description (and common expectations) it's serving http. If you proxy even your :443 virtual host to http, it'll work better.

    You might run into more issues, as the proxied application doesn't really know that it's actually served through https, but that's a different beast than this question.