http-headerscommon-lispnginx-reverse-proxyhunchentoot

How to redirect to HTTPS when Hunchentoot is behind a reverse proxy


I have a Hunchentoot application running on http://localhost:4242. This application uses a non-SSL acceptor (i.e. hunchentoot:easy-acceptor instead of hunchentoot:easy-ssl-acceptor). I set up Nginx as a reverse proxy to serve this application using SSL. This is the Nginx configuration:

server {
    listen 443 ssl;
    server_name example.test;

    ssl_certificate /etc/my-ssl/certs/example.pem;
    ssl_certificate_key /etc/my-ssl/private/example.key;

    location / {
        include /etc/nginx/proxy_params;
        proxy_pass http://localhost:4242;
    }
}

where /etc/nginx/proxy_params is

proxy_set_header Host $http_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;

The problem is that Hunchentoot does not know that the reverse proxy (i.e. Nginx) is actually serving the client using HTTPS. As a result, (hunchentoot:redirect "/some-page/") will redirect to http://example.test/some-page/ instead of https://example.test/some-page/. Hunchentoot does not take into account the X-Forwarded-Proto header.

To solve this problem, I have written an alternative redirect function which I use instead of #'hunchentoot:redirect:

(defun my-redirect (target
                    &rest args
                    &key (protocol
                           (if (string= (hunchentoot:header-in* :x-forwarded-proto)
                                        "https")
                             :https
                             :http))
                    &allow-other-keys)
  (apply #'hunchentoot:redirect target :protocol protocol args))

Is this a correct method of solving the problem? Are there better ways?

(Hunchentoot 1.3.0; SBCL 2.2.2; Nginx 1.18.0; Ubuntu 20.04)

EDIT: In Hunchentoot's issue tracker: Incorrect redirection to HTTP when application is served using HTTPS reverse proxy


Solution

  • An option is to subclass hunchentoot:easy-acceptor and specialize the acceptor-ssl-p method for your acceptor. This would enable you to call hunchentoot:redirect and have the protocol decision happen behind the scenes.

    (defclass my-easy-acceptor (hunchentoot:easy-acceptor)
      ())
    
    (defmethod hunchentoot:acceptor-ssl-p ((acceptor my-easy-acceptor))
      (string= (hunchentoot:header-in* :x-forwarded-proto) "https"))
    

    However, it seems to me (from the nginx configuration) that you are always serving HTTPS - so you could omit the logic and always return t.