Reverse proxy configuration

If you want to run OctoPrint behind a reverse proxy such as Nginx, HAProxy, Apache's mod_proxy, Caddy or traefik, you can find some configuration examples below.

On top of making sure that websocket forwarding works properly through your proxy, please pay special attention to the forwarding options and additional headers. In order for OctoPrint to properly and securely work behind one or more reverse proxies, it is of utmost importance you make sure the following headers are correctly configured:

  • X-Forwarded-For: Needs to contain the connecting client's IP, otherwise OctoPrint will think your reverse proxy is the client and possibly not behave properly or even bypass some of the security checks if you have "autologin local" configured.
  • X-Forwarded-Host or alternatively X-Forwarded-Server and X-Forwarded-Port: Needs to contain the original Host header used for the request, so server name and optionally the port number used by the connecting client to make the request. Without this configured correctly, OctoPrint might generate wrong external links and cookies and thus not work properly.
  • X-Forwarded-Proto or X-Scheme: If your reverse proxy is used to access OctoPrint through https, this needs to contain that, otherwise OctoPrint won't be able to correctly generate external links and cookies and thus not work properly.
  • X-Script-Name: If your reverse proxy makes OctoPrint accessible on a subpath (e.g. http://example.com/octoprint), this needs to be set to said subpath (e.g. /octoprint). Otherwise OctoPrint won't be able to correctly generate external links and cookies, and thus not work properly.

Failure to correctly configure your reverse proxy will lead to issues, including a possible inability to login or undermining your security!

Starting with OctoPrint 1.8.4, OctoPrint also ships with a handy test page that you can use to verify and/or debug your configuration. You can find it under /reverse_proxy_test/ on your instance, e.g. http://octopi.local/reverse_proxy_test/:

Reverse proxies and Autologin

Especially if you have Autologin configured so OctoPrint will log you in automatically when accessing it from a trusted client IP, it is critical to make sure your reverse proxy is configured to correctly forward the client's IP. If you have multiple reverse proxies in front of OctoPrint, every single one should add an X-Forwarded-For header with the client IP.

If set and sent by a trusted proxy (defined in server.reverseProxy.trustedDownstream in config.yaml, or server.reverseProxy.trustedProxies for OctoPrint 1.11.0+) OctoPrint will retrieve the client IP used for determining whether to automatically login the client from that header, ignoring any other configured trusted proxies.

So, in order to ensure that OctoPrint sees the actual client IP and not one of your reverse proxies, ensure both of the following:

  • configured all your reverse proxies to properly add/extend X-Forwarded-For - most reverse proxies should have an option for this or even do it automatically already
  • add all your reverse proxies to server.reverseProxy.trustedDownstream (or server.reverseProxy.trustedProxies for OctoPrint 1.11.0+)

To help you validate your reverse proxy configuration, the aforementioned reverse proxy test page also displays the received headers including X-Forwarded-For and the configured trusted proxies in the authenticated view, added in OctoPrint 1.10.1. You can access it through a link on the reverse proxy page, or by appending ?authenticated to the reverse proxy url, so /reverse_proxy_test/?authenticated:

Configuration Examples

config.yaml

OctoPrint up to and including 1.10.x

config.yaml contains much more than this. You will probably need to find server and create reverseProxy and trustedDownstream under it. You can specify individual IPs and CIDR ranges.

server:
  reverseProxy:
    trustedDownstream:
      - "127.0.0.0/8"
      - "::1"
      - "192.168.1.1"
      - "::ffff:192.168.1.1"

OctoPrint 1.11.0+

config.yaml contains much more than this. You will probably need to find server and create reverseProxy and trustedProxies under it. You can specify individual IPs and CIDR ranges.

server:
  reverseProxy:
    trustedProxies:
      - "192.168.1.1"
      - "::ffff:192.168.1.1"
      - "192.168.12.0/24"

OctoPrint 1.11.0+ will include 127.0.0.0/8 and ::1 automatically, unless you have set server.reverseProxy.trustLocalhostProxies to false.

Nginx

For Nginx the basic forwarding configuration is something like this:

    worker_processes  1;
    
    events {
        worker_connections  1024;
    }
    
    http {
        include            mime.types;
        default_type       application/octet-stream;
        sendfile           on;
        keepalive_timeout  65;

        map $http_upgrade $connection_upgrade {
            default upgrade;
            '' close;
        }
  
        upstream "octoprint" {
            server 127.0.0.1:5000;
        }
    
        upstream "mjpg-streamer" {
            server 127.0.0.1:8080;
        }
    
        server {
            listen       80;
            server_name  localhost;
            
            location / {
                proxy_pass http://octoprint/;
                proxy_set_header Host $http_host;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Scheme $scheme;
                proxy_http_version 1.1;

                client_max_body_size 0;    
            }

            location /webcam/ {
                proxy_pass http://mjpg-streamer/;
            }

            # redirect server error pages to the static page /50x.html
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
        }
    }

:warning: Warning

Be extra sure to include proxy_http_version 1.1 or the web socket connection will fail.

If you want to serve OctoPrint on a different base URL than / (e.g. /octoprint/), you'll need to adjust its location definition slightly by changing the location path itself, the proxy_pass target and adding the X-Script-Name header:

    location /octoprint/ {
        proxy_pass http://octoprint/; # make sure to add trailing slash here!
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-Script-Name /octoprint;
        proxy_http_version 1.1;

        client_max_body_size 0;    
    }

HAProxy 2.x

If you just want to run OctoPrint on the server root of the reverse proxy with mjpg-streamer running under /webcam, the following configuration is sufficient:

global
        maxconn 4096
        user haproxy
        group haproxy
        daemon
        log 127.0.0.1 local0 debug

defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        retries 3
        option redispatch
        option http-server-close
        option forwardfor
        maxconn 2000
        timeout connect 5s
        timeout client  15min
        timeout server  15min

frontend public
        bind *:80
        use_backend webcam if { path_beg /webcam/ }
        default_backend octoprint

backend octoprint
        option forwardfor
        server octoprint1 127.0.0.1:5000

backend webcam
        http-request replace-path /webcam/(.*)   /\1
        server webcam1  127.0.0.1:8080

If you want to make it run on a different base URL, you'll need to instruct HAProxy to send a X-Script-Name header with each forwarded request to make OctoPrint generate correct URLs on its pages:

backend octoprint
        http-request replace-path /octoprint/(.*)   /\1
        http-request set-header X-Script-Name /octoprint
        option forwardfor
        server octoprint1 127.0.0.1:5000

If you also want to run it SSL encrypted with HAProxy being the SSL terminator, you should also send a header of X-Forwarded-Proto with each forwarded request to indicate https requests. You can configure this in the backend section like this:

backend octoprint
        # ...
        http-request set-header X-Forwarded-Proto https if { ssl_fc }
        # ...

Caddy

For a basic reverse proxy on port 80 use the following:

:80 {
        handle_path /webcam/* {
                reverse_proxy localhost:8080
        }
        reverse_proxy localhost:5000
}

For a managed domain example.com, this is how to run OctoPrint on a subdomain with https termination:

octoprint.example.com {
        handle_path /webcam/* {
                reverse_proxy localhost:8080
        }
        reverse_proxy localhost:5000 {
                header_up X-Forwarded-Proto {scheme}
        }
}

If you want to add a path, here's how you'd go about this, assuming you want OctoPrint to be accessible under https://example.com/octoprint/:

example.com {
        handle_path /octoprint/* {
                handle_path /webcam/* {
                        reverse_proxy localhost:8080
                }
                reverse_proxy localhost:5000 {
                        header_up X-Forwarded-Proto {scheme}
                        header_up X-Script-Name /octoprint
                }
        }
}

Traefik

Assuming a managed domain example.com and an https entrypoint websecure, this is how to run OctoPrint on a subdomain with https termination:

http:
    routers:
      octoprint:
        rule: Host(`octoprint.example.com`)
        entrypoints:
          - websecure
        service: octoprint
    services:
      octoprint:
        loadBalancer:
          servers:
            - url: "http://octopi:80"

If you want to add a path, here's how you'd go about this, assuming you want OctoPrint to be accessible under example.com/octoprint/:

http:
    routers:
      octoprint:
        rule: Host(`example.com`) && PathPrefix(`/octoprint`)
        entrypoints:
          - websecure
        middlewares:
          - add-trailing-slash
          - octoprint-strip-prefix
          - octoprint-script-name
        service: octoprint

    middlewares:
      add-trailing-slash:
        redirectRegex:
          regex: "^(https?://[^/]+/[a-z0-9_-]+)$"
          replacement: "${1}/"
          permanent: true

      octoprint_stripprefix:
        stripPrefix:
          prefixes:
            - "/octoprint"

      octoprint_scriptname:
        headers:
          customRequestHeaders:
            X-Script-Name: "/octoprint"

    services:
      octoprint:
        loadBalancer:
          servers:
            - url: "http://octopi:80"

Apache

:warning: Not recommended

Apache is somewhat heavy weighted compared to other options, and the configuration is also a bit messy. If possible it's highly recommended to rather use another option.

For Apache (with mod_proxy, mod_proxy_http, mod_proxy_wstunnel and mod_headers enabled) the basic configuration is something among these lines:

<Location />
  ProxyPass http://<myOctoPrintHost>:5000/
  ProxyPassReverse http://<myOctoPrintHost>:5000/

  RewriteEngine on
  RewriteCond %{HTTP:UPGRADE} =websocket [NC]
  RewriteRule .* ws://<myOctoPrintHost>%{REQUEST_URI} [P,L]
</Location>

or with https:

<Location />
  RequestHeader set X-SCHEME https
  ProxyPass http://<myOctoPrintHost>:5000/
  ProxyPassReverse http://<myOctoPrintHost>:5000/

  RewriteEngine on
  RewriteCond %{HTTP:UPGRADE} =websocket [NC]
  RewriteRule .* ws://<myOctoPrintHost>%{REQUEST_URI} [P,L]
</Location>

If you want to use a different base URL, the configuration will be slightly different:

<Location /octoprint/>
  RequestHeader set X-SCRIPT-NAME /octoprint/
  ProxyPassReverse http://<myOctoPrintHost>:5000/

  RewriteEngine on
  RewriteCond %{HTTP:UPGRADE} =websocket [NC]
  RewriteRule /octoprint/(.*) ws://<myOctoPrintHost>/$1 [P,L]
  RewriteCond %{HTTP:UPGRADE} !=websocket [NC]
  RewriteRule /octoprint/(.*) http://<myOctoPrintHost>/$1 [P,L]
</Location>

If you also want to use https:

<Location /octoprint/>
  RequestHeader set X-SCRIPT-NAME /octoprint/
  RequestHeader set X-SCHEME https
  ProxyPassReverse http://<myOctoPrintHost>:5000/

  RewriteEngine on
  RewriteCond %{HTTP:UPGRADE} =websocket [NC]
  RewriteRule /octoprint/(.*) ws://<myOctoPrintHost>/$1 [P,L]
  RewriteCond %{HTTP:UPGRADE} !=websocket [NC]
  RewriteRule /octoprint/(.*) http://<myOctoPrintHost>/$1 [P,L]
</Location>
10 Likes

I have some questions. Am I correct in assuming that in your example nginx and octoprint are running on the same box?

I am trying to get nginx running on a 2nd box to rev-proxy my octopi box and while it is indeed working it is only proxying the HTML and im am kinda stuck as to why.

Yes. If things are running on a different box, you'll have to substitute 127.0.0.1:5000 accordingly.

Thank you for that. I assumed as much. I am still having issues - its like the java isnt getting through the proxy. All I am getting is straight HTML. Im sure I have overlooked something simple, but I for the life of me can not seem to figure out what it is.

This is what I am getting through nginx:

Have you put it under a different base URL (eg /octoprint/)? If so, you need to tell OctoPrint about that so it can generate the correct URLs.

If not I'd suggest to check the network tab and the js error console, try to figure out from where it's trying to load stuff from which is failing and then cross reference that with my config.

location /octo {
proxy_pass http://192.168.0.84/;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /octo;
proxy_http_version 1.1;
client_max_body_size 0;
}

This is what i got as a location block from octoprint. Im a bit confused about the rest.

Try /octo/ instead of /octo. Maybe there's an issue with base URLs not ending in a slash - I have to admit that I never tested with those.

Thank you very much for taking the time with this. Yes I realized with how old your original post was and had no comments that this might not be something others have done.

I am just sandboxing right now- but the plan is to have an nginx in the DMZ of my home network and use it as the jumping off point to everything - including my octoprint install.

So - the change you suggested did indeed bring me a lot closer to an answer.

I had some hang up with the webcam, but I think I was able to get that working with a full URL setting in octoprint.

The only remaining issue is the page is taking VERY long to initially load.

The cam feed doesnt show up and I can not click the login button for 45-60 seconds after I try to reach the page. Anyone have any ideas why it is taking so long to load and how I can speed it up.

I currently have both on an internal network segment so networking speed is not the issue. Im at a bit of a loss.

As am I. Normally I'd say either network latencies or it's a first time load, but if it stays slow on repeated reloads something else is off. If possible, try loading without the reverse proxy in between, to rule that out as the problem.

I am sure it is not latency. I am on the same small subnet and do not have much other traffic.

I have loaded it up with out nginx in the middle and it is super fast per usual.

WITH nginx doing rev proxy octoprint loads quickly - it is just the cam and login button drop down that take a long time to load.

Once the cam loads and I log in everything is super fast as it is with nginx out of the picture.

This is just a guess again, but it sounds like the initial page initialization might be taking up quite some time, causing the JS not to bind (and hence the login button to not be "armed"). When you disable the webcam, do you get the same issue? Just to rule out that it's the webcam stream through nginx somehow blocking any kind of needed request for full page initialization...

I like your thought process.

With webcam disabled it still takes "a long time" for the login button to "arm".

Next step will be a deep dive into the JS error console and the Network tab I fear. Something's hanging. Oh, also - same issue in safe mode?

Same issue in safe mode. just tried it.

There are for sure console errors :frowning:

Looks like it isn't connecting via the websocket and falling back on a long poll transport, and running into timeout issues with that.

The question is why it's not connecting via proper websocket in the first place, the Network tab might have the answer to that.

I can see the problem in the network tab, but im getting near the limit of my webapp knowledge

Hm, nope, the problem is higher up. Or at least I'd expect it to be higher up. The first thing that happens on socket initialization is a request to sockjs/info (IIRC), then a websocket will be attempted to be opened, then if that fails it's a fallback to various long polling approaches. Here I see an open websocket :face_with_raised_eyebrow: and a failed attempt to use the eventsource backend.

Frankly, this confuses me :wink:

well - good. because I am confused by it also.

This being a current sandbox env I am throwing the kitchen sink at it.

I took the stack-overflow advice and changed the owner of the /var/lib/nginx to the nginx user who is running the worker process.

The /var/cache/nginx dir was already owned by the nginx user.

I also changed the:

proxy_buffers 8 1024k; proxy_buffer_size 1024k;

all to no current avail. I have not totally broken nginx though - so I guess that is a good thing.

I have read some stack over flow threads and searched around to no avail - these seem to be the errors that are making it hang