Lighttpd Custom Configuration
Lighttpd Custom Configuration
Every user on a PMSS server runs their own lighttpd instance under their own UID. You can extend its configuration with custom fragments -- reverse proxies for Docker containers, URL rewrites, custom headers, or anything lighttpd supports.
The custom.d/ Directory
The recommended location for custom configuration is:
~/.lighttpd/custom.d/
This directory is created automatically when your account is provisioned. Every *.conf file inside it is included by your lighttpd instance at startup.
To add configuration, create a new .conf file:
<syntaxhighlight lang="bash"> nano ~/.lighttpd/custom.d/myapp.conf </syntaxhighlight>
After editing, reload lighttpd to pick up the changes:
<syntaxhighlight lang="bash"> kill -HUP $(cat ~/.lighttpd/lighttpd.pid) </syntaxhighlight>
You can also use the restart button in your web panel.
PMSS-Managed Files
Some *.conf files in custom.d/ are managed by PMSS and will be overwritten on regeneration. Do not edit these:
pmss-rclone.confpmss-qbittorrent.confpmss-deluge.confpmss-invidious.conf
Any other filename is yours. Use descriptive names: jellyfin.conf, syncthing.conf, myapp-proxy.conf.
Reverse Proxy Template
The most common use case is proxying a Docker container or local application through your lighttpd instance. Here is a working template:
<syntaxhighlight lang="lighttpd"> $HTTP["url"] =~ "^/user-USERNAME/myapp/" {
auth.require = ()
proxy.server = ( "" => ( (
"host" => "127.0.0.1",
"port" => 8080
) ) ),
proxy.header = (
"map-urlpath" => (
"/user-USERNAME/myapp/" => "/",
"/user-USERNAME/myapp" => ""
)
)
} </syntaxhighlight>
Replace USERNAME with your actual username and 8080 with the port your application listens on.
What Each Part Does
auth.require = ()
This is critical. Your lighttpd instance has global authentication that protects the /user-USERNAME/ path. Without auth.require = (), lighttpd authenticates the request and consumes the Authorization header. If your backend application also expects authentication, it never receives the credentials -- the user sees a permanent login loop.
Setting auth.require = () on the proxied path tells lighttpd: "skip authentication for this URL block; let the backend handle it." Your main panel login still protects everything else.
map-urlpath
Your application expects requests at /, but users access it at /user-USERNAME/myapp/. The map-urlpath directive strips the prefix before forwarding the request to the backend.
Without this, your application receives the full path (/user-USERNAME/myapp/page) and cannot match its routes.
Two entries are needed: one with a trailing slash, one without. This handles both /user-USERNAME/myapp/ and /user-USERNAME/myapp.
Choosing a Port
Your application (Docker container or otherwise) must listen on a port that isn't already in use. Bind to 127.0.0.1 so it's only reachable through your lighttpd proxy, not directly from the internet.
Pick any unused port above 1024. Check what's already in use:
<syntaxhighlight lang="bash"> ss -tlnp </syntaxhighlight>
PMSS allocates service ports randomly in the 2000-38000 range. Before binding to any port, check what is already in use with ss -tlnp above.
For Docker containers, bind the port explicitly to loopback:
<syntaxhighlight lang="bash"> docker run -d --name myapp -p 127.0.0.1:8080:80 myimage </syntaxhighlight>
Example: Proxying a Docker Container
Full walkthrough: running a web application in Docker and making it accessible through your panel.
1. Start the container:
<syntaxhighlight lang="bash"> docker run -d --name filebrowser \
-p 127.0.0.1:9090:80 \ -v ~/data:/srv \ filebrowser/filebrowser
</syntaxhighlight>
2. Create the proxy configuration:
<syntaxhighlight lang="bash"> cat > ~/.lighttpd/custom.d/filebrowser.conf << 'EOF' $HTTP["url"] =~ "^/user-johndoe/filebrowser/" {
auth.require = ()
proxy.server = ( "" => ( (
"host" => "127.0.0.1",
"port" => 9090
) ) ),
proxy.header = (
"map-urlpath" => (
"/user-johndoe/filebrowser/" => "/",
"/user-johndoe/filebrowser" => ""
)
)
} EOF </syntaxhighlight>
3. Reload lighttpd:
<syntaxhighlight lang="bash"> kill -HUP $(cat ~/.lighttpd/lighttpd.pid) </syntaxhighlight>
4. Access it:
Visit https://SERVER.pulsedmedia.com/user-johndoe/filebrowser/
5. (Optional) Add a tab:
Add to ~/.customFrames (see Custom Tabs):
filebrowser|Web File Browser|Files|/user-johndoe/filebrowser/
Legacy: ~/.lighttpd/custom
The single file ~/.lighttpd/custom (no directory, no extension) is also included by lighttpd. This is a legacy mechanism that still works. However, custom.d/ is preferred because:
- You can organize configuration into separate files per application.
- You can enable/disable an application by renaming its file (e.g., append
.disabled). - PMSS manages its own fragments in
custom.d/without touching your files.
Important: The ~/.lighttpd/custom file must exist if your lighttpd configuration references it. If it's missing, lighttpd will fail to start. If you don't need it, keep it as an empty file:
<syntaxhighlight lang="bash"> touch ~/.lighttpd/custom </syntaxhighlight>
Troubleshooting
lighttpd won't start after config change
Check the error log:
<syntaxhighlight lang="bash"> cat ~/.lighttpd/error.log </syntaxhighlight>
Common causes:
- Syntax error in your
.conffile -- lighttpd's error message will point to the line. - Missing
~/.lighttpd/customfile (see Legacy section above).
Test your configuration before reloading:
<syntaxhighlight lang="bash"> lighttpd -t -f ~/.lighttpd.conf </syntaxhighlight>
Proxy returns 502 Bad Gateway
The backend application isn't running or isn't listening on the port you specified. Check:
<syntaxhighlight lang="bash"> ss -tlnp | grep PORT docker ps </syntaxhighlight>
Permanent login loop (401 errors)
You forgot auth.require = () in your proxy block. Without it, lighttpd's global authentication consumes the credentials before they reach your backend. Add the empty auth directive and reload.
Application works directly but not through the proxy
Test direct access:
<syntaxhighlight lang="bash"> curl http://127.0.0.1:PORT/ </syntaxhighlight>
If that works but the proxied URL doesn't, check:
- Is lighttpd actually using your config? Reload it.
- Is
map-urlpathcorrect? The application must see/, not the full proxied path.
See Also
- Custom Tabs -- adding tabs to your web panel for proxied applications
- Docker Rootless -- running containers on your seedbox
- Invidious -- PMSS-integrated private YouTube frontend (auto-configured proxy)
- Install Media Stack -- one-command Jellyfin/Sonarr/Radarr installation