Struggling to get HedgeDoc working behind HAProxy - clicking "log in" just closes the username/password box

My version of HedgeDoc is: 1.9.7

What I expected to happen:

When going to docs.example.com, I expect to be able to access and log into my HedgeDoc instance.

HedgeDoc is running in a Portainer stack (docker compose). I am accessing it via HAProxy - I’m connecting to the frontend with HTTPS, and HAProxy is communicating with HedgeDoc via HTTP.

What actually happened:

I can access docs.example.com. When I click “sign in”, the login window pops up. I enter email/password configured by the CLI user manager; when I click “sign in” in the box, the box simply disappears. If I click “new guest note”, it shows in the top right corner that I am offline.

There are no errors in the HedgeDoc logs or the browser console.

If I change CMD_PROTOCOL_USESSL to false, CMD_URL_ADDPORT to true, and CMD_DOMAIN to the machine’s local IP address, I can access and use hedgedoc at its IP:port.

I already tried:

I’ve played around with the environment variables - accessing the backend via HTTPS, moving it to a different port, and different values of CMD_PORT, CMD_HOST, CMD_HSTS_ENABLE, etc.

I have a feeling the issue is related to the following sentence in the docs: “The reverse proxy must allow websocket Upgrade requests at path /sockets.io/.” However, I can’t figure out how to check on this.

version: '3'
services:
  database:
    image: postgres:13.4-alpine
    environment:
      - POSTGRES_USER=hedgedoc
      - POSTGRES_PASSWORD=sanitized
      - POSTGRES_DB=hedgedoc
    volumes:
      - database:/var/lib/postgresql/data
    restart: always
  app:
    # Make sure to use the latest release from https://hedgedoc.org/latest-release
    image: quay.io/hedgedoc/hedgedoc:latest
    environment:
      - CMD_DB_URL=postgres://hedgedoc:sanitized@database:5432/hedgedoc
      - CMD_DOMAIN=docs.example.com
      - CMD_PROTOCOL_USESSL=true
      - CMD_URL_ADDPORT=false
    volumes:
      - uploads:/hedgedoc/public/uploads
    ports:
      - "3000:3000"
    restart: "unless-stopped"
    depends_on:
      - database
volumes:
  database:
  uploads:

Finally figured it out! In pfSense, in the HAProxy frontend advanced settings, there’s a checkbox titled “Use ‘forwardfor’ option”. I recalled that I’d seen similar language in the example Nginx/Apache configs, so I tried enabling it and it did the trick.

It might be obvious to someone who knows Nginx/Apache or who is more experienced with networking in general, but it was total blind luck that I figured this out. It would be hugely helpful to update the docs to indicate that the reverse proxy must pass on the “X-Forwarded-For” header, and it would be useful to point out the existence of this option in HAProxy (though I know many use HAProxy outside of pfSense where they are working with text configurations rather than a GUI).

For anyone else using HAProxy, here are sanitized versions of my configs:
HAProxy:

frontend haproxy_bind
	bind			172.20.0.99:443 name 172.20.0.99:443   ssl crt-list /var/etc/haproxy/haproxy_bind.crt_list
	bind			172.20.0.99:80 name 172.20.0.99:80
	mode			http
	log			global
	option			http-keep-alive
	option			forwardfor
	acl https ssl_fc
	http-request set-header		X-Forwarded-Proto http if !https
	http-request set-header		X-Forwarded-Proto https if https
	timeout client		30000
	acl			https	dst_port 443
	acl			docs	var(txn.txnhost) -m beg -i docs.
	acl			aclcrt_haproxy_bind	var(txn.txnhost) -m reg -i ^example\.com(:([0-9]){1,5})?$
	acl			aclcrt_haproxy_bind	var(txn.txnhost) -m reg -i ^([^\.]*)\.example\.com(:([0-9]){1,5})?$
	http-request set-var(txn.txnhost) hdr(host)
	use_backend hedgedoc_ipvANY  if  docs https aclcrt_haproxy_bind

backend hedgedoc_ipvANY
	mode			http
	id			121
	log			global
	timeout connect		30000
	timeout server		30000
	retries			3
	server			servername 172.20.0.6:3002 id 117

Docker compose:

version: '3'
services:
  database:
    image: postgres:13.4-alpine
    environment:
      - POSTGRES_USER=hedgedoc
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=hedgedoc
    volumes:
      - database:/var/lib/postgresql/data
    restart: always
  app:
    # Make sure to use the latest release from https://hedgedoc.org/latest-release
    image: quay.io/hedgedoc/hedgedoc:latest
    environment:
      - CMD_DB_URL=postgres://hedgedoc:password@database:5432/hedgedoc
      - CMD_DOMAIN=docs.example.com
      - CMD_PROTOCOL_USESSL=true
      - CMD_URL_ADDPORT=false
    volumes:
      - uploads:/hedgedoc/public/uploads
    ports:
      - "3002:3000"
    restart: "unless-stopped"
    depends_on:
      - database
volumes:
  database:
  uploads:

For completeness, here’s the relevant snippets from my haproxy.conf to do TLS termination and then proxy to hedgedoc running in a container, on a dedicated subdomain:

frontend default_fe
        acl     docs            hdr(host)       docs.example.org
        http-request redirect   scheme https code 301 if docs  !{ ssl_fc }

        use_backend             hedgedoc_be     if docs

...

backend hedgedoc_be
        http-request      set-header      x-forwarded-host    %[req.hdr(host)]
        http-request      set-header      x-forwarded-proto   https if { ssl_fc }
        http-request      set-header      x-forwarded-server  %[req.hdr(host)]
        http-request      set-header      x-real-ip           %[src]
        server            hedgedoc        123.45.67.89:3000

Based on what you have above, not all of these headers might be required.⏎

1 Like