Skip to content

[Bug] .htaccess relative RewriteRule leaks internal port :8080 into 301 Location header when run behind a reverse proxy #200

Closed

Description

@ThomDietrich

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: 8080

Observed:

$ 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:ro

Assumes 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:

  1. Emit an absolute URL in the shipped .htaccess with scheme detection from X-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]
  2. Set UseCanonicalName On + a configurable ServerName via an env var (e.g. LB_SERVER_NAME).
  3. Trust X-Forwarded-* in the image's Apache config. The image already ships remoteip.conf with RemoteIPHeader X-Real-IP, but doesn't tell Apache the forwarded scheme. Adding SetEnvIf X-Forwarded-Proto "^https$" HTTPS=on + UseCanonicalPhysicalPort Off might be enough.
  4. Reconsider Listen 8080. If the motivation is running Apache as a non-root user, CAP_NET_BIND_SERVICE on the container (or the existing www-data + authbind-style trick) works on port 80 too. Going back to Listen 80 sidesteps 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

  1. self-assigned this
    on Apr 24, 2026
  2. colisee commented on Apr 24, 2026

    @colisee
    Collaborator

    @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.

  3. JohnVillalovos commented on Apr 24, 2026

    @JohnVillalovos
    Collaborator
  4. colisee commented on Apr 24, 2026

    @colisee
    Collaborator

    @ThomDietrich, I confirm the issue with the nginx-proxy docker as well.

    I tested succesfully your first 2 suggestions:

    1. Change the .htaccess file
      RewriteCond %{REQUEST_URI} !^/Web(/|$)
      RewriteRule ^(.*)$ %{REQUEST_SCHEME}://%{HTTP_HOST}/Web/$1 [R=301,L]
      
    2. Change the /etc/apache2/sites-available/000-default.conf file
      ServerName <HOST_NAME>
      

    The 3rd suggestion failed.

    I will implement solution-1 through the container entrypoint inside my forked repo

  5. colisee commented on Apr 25, 2026

    @colisee
    Collaborator

    Related PR: LibreBooking/librebooking#1358

    Sorry @JohnVillalovos , I didn't see your comment.
    I tested the removal of the R=301 flag and it works perfectly.

    Henceforth, the .htaccess file 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.

  6. ikke-t commented on Apr 26, 2026

    @ikke-t
    Contributor

    This finally exlains the odd problems with cloudflared.

  7. ThomDietrich commented on Apr 26, 2026

    @ThomDietrich
    Author

    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!

  8. JohnVillalovos commented on Apr 26, 2026

    @JohnVillalovos
    Collaborator

    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.

    @ThomDietrich

    After you login. Does the URL have '/Web/' in it or not? Just curious. Also what is your config.php -> settings -> script.url set to? Or LB_SCRIPT_URL setting?

  9. colisee commented on Apr 27, 2026

    @colisee
    Collaborator

    @JohnVillalovos : While testing, I came across an odd issue. Could you please check on your side as well:

    Steps:

    1. Cleanup the volumes: docker compose down --volumes
    2. Start the containers: docker compose up --detach
    3. Access the installation page https://buchung.example.com/install. Do you end up with a connection error due to the wrong URL https://buchung.example.com:8080/Web/install ?
    4. Add a trailing / to the above URL -> https://buchung.example.com/install/. Does it work now?

    In my case, I am not using the traefik container but nginxproxy/nginx-proxy

  10. JohnVillalovos commented on Apr 27, 2026

    @JohnVillalovos
    Collaborator

    @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.

  11. colisee commented on Apr 27, 2026

    @colisee
    Collaborator

    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)

  12. JohnVillalovos commented on Apr 27, 2026

    @JohnVillalovos
    Collaborator

    @colisee I have the new redirect rule installed as a test ( no R=301). If I go to just the website without /Web I don't get redirected and it works fine. For example http://jlvillal.example.com/ works and no redirect.

    Doing http://jlvillal.example.com/install I get redirected to http://jlvillal.example.com/Web/install/

  13. colisee commented on Apr 29, 2026

    @colisee
    Collaborator

    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 :8080 port 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 a R=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.

  14. colisee commented on Apr 29, 2026

    @colisee
    Collaborator

    Using docker and Traefik, it looks like I now have a working setup.

    In file .htaccess, I reverted back to the original R=301 rewriteRule 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.yml file:

    name: librebooking
    
    services:
      db:
        image: linuxserver/mariadb:10.6.13
        volumes:
          - db_conf:/config
        env_file:
          - db.env
      app:
        image: librebooking:develop
        depends_on:
          - db
        volumes:
          - app_conf:/config
          - app_img:/var/www/html/Web/uploads/images
          - app_res:/var/www/html/Web/uploads/reservation
        env_file:
          - lb.env
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.librebooking.rule=Host(`book.localhost`)"
          - "traefik.http.routers.librebooking.entrypoints=web"
          - "traefik.http.services.librebooking.loadbalancer.server.port=8080"
      traefik:
        image: traefik:v3.6
        command:
          - "--api.insecure=true"
          - "--providers.docker=true"
          - "--providers.docker.exposedbydefault=false"
          - "--entrypoints.web.address=:80"
        ports:
          - "80:80"
          - "8081:8080"
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock
    
    volumes:
      db_conf:
      app_conf:
      app_img:
      app_res:
  15. colisee commented on Apr 29, 2026

    @colisee
    Collaborator

    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)

  16. JohnVillalovos commented on Apr 29, 2026

    @JohnVillalovos
    Collaborator

    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)

    Thanks @colisee for all the investigation on this! Please let me know if you think we should make changes upstream.

  17. colisee commented on Apr 30, 2026

    @colisee
    Collaborator

    @JohnVillalovos no changes in upstream needed. Changes required for this repo only.

  18. ikke-t commented on Apr 30, 2026

    @ikke-t
    Contributor

    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 *:8080 binds 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.

  19. colisee commented on Apr 30, 2026

    @colisee
    Collaborator

    Actually, the apache web server in the image listens to ports 8080 and 8443 only (file /etc/apache2/ports.conf is 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions