27 Mar 2025 - tsp
Last update 27 Mar 2025
2 mins
When exposing a JupyterLab server to the internet, especially one running on an internal machine, it’s important to do so securely and robustly. This quick guide covers how to place JupyterLab behind an Apache 2.4 reverse proxy with HTTPS termination, using JupyterLab 4.2.5 running on an internal host. We walk through the necessary Apache configuration, address common websocket issues, and explain what must be configured on the JupyterLab side to ensure proper operation.
In our setup, JupyterLab runs on an internal machine with IP 192.0.2.100 on port 8888. Apache httpd acts as the HTTPS-facing reverse proxy and is accessible to the public via jupyter.example.com.
The Apache VirtualHost configuration enables both standard HTTP proxying and websocket tunneling. Websockets are required for many interactive features in JupyterLab, including launching kernels and receiving output. Misconfigured websocket proxying is a common cause of broken kernel behavior, such as failures to start or cells not producing output.
Here’s the relevant Apache 2.4 configuration:
<VirtualHost *:443>
ServerName jupyter.example.com
ServerAdmin webmaster@example.com
DocumentRoot /usr/www/jupyter.example.com/www
SSLOptions +StdEnvVars
SSLVerifyClient optional
SSLVerifyDepth 5
SSLCertificateFile "/usr/www/jupyter.example.com/conf/ssl.cert"
SSLCertificateKeyFile "/usr/www/jupyter.example.com/conf/ssl.key"
SSLCertificateChainFile "/usr/www/jupyter.example.com/conf/ssl.cert"
SSLCACertificateFile "/usr/local/etc/ssl/ca01_01.cert"
RewriteEngine On
RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule /(.*) ws://192.0.2.100:8888/$1 [P,L]
ProxyRequests Off
ProxyVia Off
ProxyPreserveHost On
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
ProxyPass / http://192.0.2.100:8888/ nocanon
ProxyPassReverse / http://192.0.2.100:8888/
RequestHeader edit Origin ^(.*)$ $1
RequestHeader edit Referer ^(.*)$ $1
Header unset Content-security-policy
Header unset Content-security-policy-report-only
Header always set Access-Control-Allow-Origin "*"
</VirtualHost>
This setup forwards normal HTTP requests and websocket upgrade requests to the internal JupyterLab server. The key parts are the RewriteCond
rules and RewriteRule
, which ensure websocket connections are passed properly. The ProxyPreserveHost
and X-Forwarded-Proto
header help the internal server understand the original request’s context.
On the internal host running JupyterLab, you must configure it to listen on all interfaces (0.0.0.0), accept connections from the proxy host, and allow remote access. This is done in your ~/.jupyter/jupyter_lab_config.py
file:
c.ServerApp.port = 8888
c.ServerApp.local_hostnames = ['jupyter.example.com']
c.ServerApp.ip = '0.0.0.0'
c.ServerApp.allow_remote_access = True
c.ServerApp.allow_origin = '*'
Setting the port
to 8888 matches what Apache forwards to. Binding to 0.0.0.0
ensures the JupyterLab server accepts connections from any interface, including from Apache on the same or a different host. The local_hostnames
setting tells JupyterLab which hostnames are allowed to appear in requests; this must include the public domain name used by clients. allow_remote_access
is necessary to let the proxy connect from another machine, and allow_origin = '*'
ensures CORS doesn’t block legitimate browser requests that may originate from outside the internal network.
Running JupyterLab behind an Apache reverse proxy is a practical way to provide secure, HTTPS access to a notebook server running on an internal machine. The main pitfall is usually related to websocket support; getting the rewrite and proxy rules correct is critical for kernel functionality. With the configurations above, JupyterLab should be accessible from the outside while maintaining flexibility and a reasonable level of security.
This article is tagged:
Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)
This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/