Reverse proxy shenanigans

Hi All,

I’m trying to put a big site live and I could use an extra pair of eyes (or brains :sweat_smile:)


Live (production) url:
Server url:

The is a reverse proxy for

Partial solution

The frontend site works completely as expected, what did I do to make this happen:

1/ passed the ‘index’ and ‘media’ urls with the Kirby instance

$kirby = new Kirby([
    'roots' => [
        'index'    => __DIR__,
        'base'     => __DIR__,
        'site'     => "{$rootFolder}/site",
        'storage'  => "{$storageFolder}",
        'content'  => "{$storageFolder}/content",
        'accounts' => "{$storageFolder}/site/accounts",
        'cache'    => "{$storageFolder}/site/cache",
        'media'    => "{$storageFolder}/media",
        'sessions' => "{$storageFolder}/site/sessions",
        'config'   => "{$rootFolder}/site/config"
    'urls' => [
        'index' => $realUrl,
        'media'  => $realUrl . $media,

2/ Overwrite the “url” in my config.php

return [
    'url' => fn() => '',

This all works like a charm so all good until there.

Problem :thinking:

1 problem though, the panel isn’t working. I just get a blank page. I see simular behaviour like mentioned on Installation and login failing in 3.6 (reverse proxy environments) · Issue #3947 · getkirby/kirby · GitHub.

The panel page has no js errors, everything loads but the screen stays blank.
When I inspect the rendered HTML I see that that the json const renders 2 url’s

I’ve tracked down the origin for that $url to src\Panel\View.php (line 199) where Url::current() is being passed and I believe that the Url::current() is the culprit.

When I, for testing purposes, change the Url::current() in the View.php to a hardcoded the panel does completely work.

The question

Is there anything more we can do to do overwrite that Url (in a clean way) or is it a bug in Kirby? I’ve been trying to see what goes wrong in the Kirby\Http\Url but i’m afraid this is above my PHP level :man_facepalming:

I would agree that Kirby should be able to be configured with a URL when running behind a reverse proxy. However, as a workaround, a reverse proxy can be configured to compensate for that.

I am running a reverse proxy based on apache which let me access any number of websites which are actually running on my home laptop. Just tested a fresh installation of Kirby starterkit and I could access the panel for installation and work on it like I would be at home.

If you are interested, I can share the settings or give you access to a fresh starterkit to check out yourself.

If you were able to solve this without hacking core files I’m very interested in what settings you use! :blush: Hopefully can, what you did, inspire to solve the problem on other proxies. (we are not working with apache proxies but some enterprise lvl shizzle, managed by an Infrastructure departement)

Well, no hacking, it is all about instructing the proxy webserver to do what Kirby should have done - replacing all protocols and server urls which refers to the application server to the client-facing ones. Most of the job is done by the basic reverse proxy configuration settings (all of my Kirby installations are available on http://localhost:8881):

 ProxyRequests Off
 ProxyPass / http://localhost:8881/
 ProxyPassReverse / http://localhost:8881/
 ProxyPreserveHost On

But I encountered several additional settings which are needed because Kirby still produces self-referencing urls which are wrong (i.e. point to the wrong protocol/url). These are:

RequestHeader set X-Forwarded-Proto "https"

This makes sure that the protocol is added to the header (the basic settings above will already set the X-Forwarded-Host header, but not this one).

 ProxyHTMLDocType "<!DOCTYPE html>"
 ProxyHTMLEnable On
 ProxyHTMLExtended On
 ProxyHTMLURLMap "http:" "https:"

These settings make sure that the correct doctype is set, and that the client-facing URL is changed in all HTML which comes from the backend, not only in header values.

 AddOutputFilterByType SUBSTITUTE application/x-javascript application/json
 Substitute "s|http:|https:|nq"

These directives also fixes any remaining references in HTML. Maybe not necessarily needed.

In order to make these settings work, some additional apache modules are needed, namely proxy_html with its dependencies and substitute for the last two directives, if necessary.

Also, I think there is a requirement to run the backend on http only, because otherwise the content which comes from the backend needs to be unencrypted which requires additional settings on the proxy. I noted that your backend URL is on https which is not necessary from my point of view, but your environment might differ. My backend application is only accessible from localhost, not from the outer world, thats why it is ok to terminate ssl on the proxy. This also reduces some of the computing overhead (which of course will reintroduced by the extended HTML filter and substitute).