Repository navigation
[Bug] .htaccess relative RewriteRule leaks internal port :8080 into 301 Location header when run behind a reverse proxy #200
Description
Acknowledgements
- This is a bug and not a question. Questions should be asked in the Discussions area.
- This project is maintained 100% by volunteers. I understand that there are no guarantees on when (or if) this will be addressed, and that the most effective way to see this resolved is for me to submit a Pull Request.
Describe the bug
The librebooking/librebooking:develop Docker image listens on port 8080 internally (the image's build script rewrites Apache's Listen 80 → Listen 8080), but ships a /var/www/html/.htaccess whose catch-all redirect uses a relative target:
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/Web(/|$)
RewriteRule ^(.*)$ /Web/$1 [R=301,L]When Apache emits this as an external 301, it has to expand /Web/$1 into an absolute URL for the Location: header. With UseCanonicalName Off (the default) and no explicit ServerName set in the image's vhost, Apache builds the URL as http://<Host-header-hostname>:<physical-listen-port>/… — i.e. http://<host>:8080/Web/.
For any deployment behind a reverse proxy (traefik, nginx-proxy, Caddy, ...) where port 8080 is not exposed externally, browsers receive a redirect they cannot follow, and the app is unusable on its landing page.
To Reproduce
Minimal compose fragment (traefik as the proxy — same bug appears with any reverse proxy that doesn't preserve the internal port):
services:
app:
image: librebooking/librebooking:develop
labels:
traefik.enable: true
traefik.http.routers.lb.rule: Host(`booking.example.com`)
traefik.http.routers.lb.tls.certresolver: my-resolver
traefik.http.services.lb.loadbalancer.server.port: 8080Observed:
$ curl -sSI https://booking.example.com/
HTTP/2 301
location: http://booking.example.com:8080/Web/
server: Apache/2.4.66 (Debian)
Expected behavior
location: https://booking.example.com/Web/
Screenshots
No response
Additional context
Why this bites now
Setting .htaccess's RewriteRule target as a relative path was fine when Apache listened on port 80 — the port gets elided from redirect URLs (80 is the default for HTTP), and a proxy's http→https redirect handles the scheme upgrade. Since the image now listens on 8080, Apache dutifully includes :8080 in the Location, which is invisible to the outside world and cannot be followed.
The app's own script.url in config.php doesn't help: this 301 is emitted at the Apache/.htaccess layer before any PHP runs.
Workaround for other users hitting this
Bind-mount a .htaccess that emits an absolute HTTPS URL using %{HTTP_HOST}:
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/Web(/|$)
RewriteRule ^(.*)$ https://%{HTTP_HOST}/Web/$1 [R=301,L]volumes:
- ./htaccess-override:/var/www/html/.htaccess:roAssumes the proxy terminates TLS (the typical behind-proxy deployment) and forwards Host: without a port (traefik/nginx/Caddy defaults).
Possible upstream fixes
A few directions — not prescriptive, just for discussion:
- Emit an absolute URL in the shipped
.htaccesswith scheme detection fromX-Forwarded-Proto:RewriteCond %{HTTP:X-Forwarded-Proto} =https [OR] RewriteCond %{HTTPS} =on RewriteRule ^(.*)$ https://%{HTTP_HOST}/Web/$1 [R=301,L] RewriteCond %{HTTP:X-Forwarded-Proto} !=https RewriteCond %{HTTPS} !=on RewriteRule ^(.*)$ http://%{HTTP_HOST}/Web/$1 [R=301,L]
- Set
UseCanonicalName On+ a configurableServerNamevia an env var (e.g.LB_SERVER_NAME). - Trust
X-Forwarded-*in the image's Apache config. The image already shipsremoteip.confwithRemoteIPHeader X-Real-IP, but doesn't tell Apache the forwarded scheme. AddingSetEnvIf X-Forwarded-Proto "^https$" HTTPS=on+UseCanonicalPhysicalPort Offmight be enough. - Reconsider
Listen 8080. If the motivation is running Apache as a non-root user,CAP_NET_BIND_SERVICEon the container (or the existingwww-data+authbind-style trick) works on port 80 too. Going back toListen 80sidesteps the whole class of "physical-port-leaks-into-URL" issues.
LibreBooking version
librebooking/librebooking:develop, Image ID: sha256:238a02e1d4bd3e5d3621ebe93def8057a131debdc462b098ded49a48e45b40bd - Image created: 2026-04-14T04:18:57Z - Apache: 2.4.66 (Debian) - Deployment: behind traefik v3 with TLS termination (reproducible with any similar reverse proxy)
Activity
colisee commentedon Apr 24, 2026
@ThomDietrich , thank you very much for creating the issue and for having already investigated!
Please, stay tuned, as I will check the issue, using the nginx-proxy docker and report.
JohnVillalovos commentedon Apr 24, 2026
Related PR: LibreBooking/librebooking#1358
colisee commentedon Apr 24, 2026
@ThomDietrich, I confirm the issue with the nginx-proxy docker as well.
I tested succesfully your first 2 suggestions:
.htaccessfile/etc/apache2/sites-available/000-default.conffileThe 3rd suggestion failed.
I will implement solution-1 through the container entrypoint inside my forked repo
colisee commentedon Apr 25, 2026
Sorry @JohnVillalovos , I didn't see your comment.
I tested the removal of the
R=301flag and it works perfectly.Henceforth, the
.htaccessfile in the upstream repo should be changed.On my side, I will add a patch in the container entrypoint to take care of this issue.
ikke-t commentedon Apr 26, 2026
This finally exlains the odd problems with cloudflared.
ThomDietrich commentedon Apr 26, 2026
Thanks @colisee and @JohnVillalovos for the quick turnaround! I tested the
[L]-only form from librebooking#1358 on my setup (traefik in front of librebooking/librebooking:develop) by bind-mounting an overridden .htaccess. Works perfectly: https://buchung.example.com/ now returns 200 directly via internal rewrite, with no Location header and therefore no port leakage.Both PRs make sense to me and they're complementary. #1358 fixes the upstream source, while #201 ensures existing tagged releases (v4.3.0, v4.2.0, …) also pick up the fix without needing per-branch backports. Thanks again for the fast fix!
JohnVillalovos commentedon Apr 26, 2026
@ThomDietrich
After you login. Does the URL have '/Web/' in it or not? Just curious. Also what is your
config.php->settings->script.urlset to? OrLB_SCRIPT_URLsetting?colisee commentedon Apr 27, 2026
@JohnVillalovos : While testing, I came across an odd issue. Could you please check on your side as well:
Steps:
docker compose down --volumesdocker compose up --detachhttps://buchung.example.com/install. Do you end up with a connection error due to the wrong URLhttps://buchung.example.com:8080/Web/install?https://buchung.example.com/install/. Does it work now?In my case, I am not using the traefik container but nginxproxy/nginx-proxy
JohnVillalovos commentedon Apr 27, 2026
@colisee I don't really use the Docker image. So I would need to set it up in order to test.
I don't see that issue on a non-Docker install though.
colisee commentedon Apr 27, 2026
No need to install docker, nor podman if you are not using containers.
In that case, with your setup, could you try the 2 URL for accessing the install page (with and without the trailing /)
@ThomDietrich @ikke-t : please, feel free to test as well with your containers (possibly with an empty database)
JohnVillalovos commentedon Apr 27, 2026
@colisee I have the new redirect rule installed as a test ( no
R=301). If I go to just the website without/WebI don't get redirected and it works fine. For examplehttp://jlvillal.example.com/works and no redirect.Doing
http://jlvillal.example.com/installI get redirected tohttp://jlvillal.example.com/Web/install/colisee commentedon Apr 29, 2026
I am really getting mad with this issue! I switched to traefik as a reverse proxy, to be in a comparable situation with @ThomDietrich
It looks like the insertion of the
:8080port in the Redirect (when the RewriteRule-R flag is used) comes from the definition of<VirtualHost *:8080>in file/var/www/html/sites-available/000-default.conf. I did a test by changing the virtualhost definition to<VirtualHost *>and I no longer have :8080 redirections...@JohnVillalovos . WITHOUT the R flag in the RewriteRule, I am getting
AH01630: client denied by server configuration: /var/www/html/scripts/phpscheduleit.js, referer: http://book.localhost/. The issue is that phpscheduleit.js is not inside /var/www/html/scripts but inside /var/www/html/scripts/Web. This could be the reason why we had aR=301flag (R or R=302 should also be OK). Could you please re-check?@ThomDietrich . Could you please share your complete docker-compose.yml for services traefik and librebooking ? Please. omit passwords, if any.
colisee commentedon Apr 29, 2026
Using docker and Traefik, it looks like I now have a working setup.
In file
.htaccess, I reverted back to the originalR=301rewriteRule flag (I removed the related sed code from bin/entrypoint.sh)In file
/etc/apache2/sites-available/000-default.conf, I moved to<VirtualHost *>from<VirtualHost *:8080>(I changed the related sed statement in bin/build_sys.sh)Here is the content of my
docker-compose.ymlfile:colisee commentedon Apr 29, 2026
The above setup also works now when using nginxproxy instead of traefik.
I am going to run a final test with podman (instead of docker) to confirm that everything is working OK.
If that is the case, then I will push the new changes to my forked repo (they should also show up in the related PR)
JohnVillalovos commentedon Apr 29, 2026
Thanks @colisee for all the investigation on this! Please let me know if you think we should make changes upstream.
colisee commentedon Apr 30, 2026
@JohnVillalovos no changes in upstream needed. Changes required for this repo only.
ikke-t commentedon Apr 30, 2026
I'm surpriced to find this makes a difference. But I take it if you say so. I have always thought the port 8080 and
VirtualHost *:8080binds apache to listen to given port only for given virtual host. I didn't realize it would affect how port is handled outside of apache? Other than what those apache internal connection parameters include it in parts of variable strings.colisee commentedon Apr 30, 2026
Actually, the apache web server in the image listens to ports 8080 and 8443 only (file
/etc/apache2/ports.confis modified when building the image). At this stage, port 8443 (ssl) is useless, for I didn't activate the ssl configuration. This means that the communication between the reverse-proxy and the librebooking container is not encrypted. In a future release, I could activate the ssl encryption, if requested.While doing my research, I discovered that when there is a redirection (typically one triggered by the index option), the web server will add the port specified in the VirtualHost statement when it is non-standard (ie. not 80 nor 443). By removing the port in VirtualHost, I no longer have the port inclusion issue.