Nginx serve static website and kirby panel?

Hello,

Kirby is working nicely (including accessing panel thanks to /edition slug) with this nginx config :

location / {

    # Path to source
    alias /var/www/kirby/www/;


    # Default indexes and catch-all
    index index.php index.html;
    try_files $uri $uri/ /index.php?$args =404;

    # Prevent useless logs
    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    # Deny access to hidden files and directories
    location ~ ^/(.+/|)\.(?!well-known\/) {
        deny all;
    }

# Execute and serve PHP files
location ~ [^/]\.php(/|$) {
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    fastcgi_pass unix:/var/run/php/php8.2-fpm-kirby.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param REMOTE_USER $remote_user;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_param SCRIPT_FILENAME $request_filename;
}

# Remove index.php from URL needed on some cms
if (!-e $request_filename) {
    rewrite ^(.*)$ /index.php?q=$1 last;
}

}

# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# Block bad bots
if ($http_user_agent ~* "(BlackWidow|ChinaClaw|larbin|HTTrack|Wget|WebCopier|Go!Zilla|Zeus|EmailSiphon|EmailWolf|Express WebPictures)") {
    return 403;
}

# Disable access to hidden files except .well-known
location ~ /\.(?!well-known) {
    deny all;
}

# Restrict access to system and content files
location ~* ^/(site|kirby)/ {
    deny all;
}

location ~* ^/content/.*\.(txt|md|mdown)$ {
    deny all;
}

# Disable directory listing
autoindex off;

# Disable ETags
etag off;

# MIME and charset
include mime.types;
default_type application/octet-stream;

And that works perfectly. It serves all pages and I can acces Kirby admin panel when I visit kirby.domain.tld/edition (I choose this slug in Kirby config file).

Now, I made this plugin ( GitHub - jonathan-reisdorf/kirby-static-site-generator: Static site generator plugin for Kirby 3/4/5. With this plugin you can create a directory with assets, media and static html files generated from your pages. The result is an even faster site. · GitHub ) build a static version of my website in the /var/www/kirby/static/ directory.

What I would like is for nginx to serve the static website and I want also nginx to server kirby panel when the user visit kirby.domain.tld/edition .

Here is what I wrote (but doesn’t work so far) :

location / {

    # Path to source
    root /var/www/kirby/static/;

    # Default indexes and catch-all
    index index.php index.html;
    try_files $uri $uri/ /index.php?$args =404;

    # Prevent useless logs
    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    # Deny access to hidden files and directories
    location ~ ^/(.+/|)\.(?!well-known\/) {
        deny all;
    }

# redirect /edition (no slash) to /edition/
location = /edition {
    return 301 /edition/;
}

# handle /edition PHP app (top-level location)
location ^~ /edition/ {
    alias /var/www/kirby/www/;
    index index.php index.html;
    try_files $uri $uri/ /edition/index.php?$args =404;

    # Remove index.php from URL needed on some cms
    if (!-e $request_filename) {
        rewrite ^(.*)$ /index.php?q=$1 last;
    }
}

# PHP processing for files under /edition
location ~ [^/edition]\.php(/|$) {
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    fastcgi_pass unix:/var/run/php/php8.2-fpm-kirby.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param REMOTE_USER $remote_user;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_param SCRIPT_FILENAME $request_filename;
}

# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# Block bad bots
if ($http_user_agent ~* "(BlackWidow|ChinaClaw|larbin|HTTrack|Wget|WebCopier|Go!Zilla|Zeus|EmailSiphon|EmailWolf|Express WebPictures)") {
    return 403;
}

# Disable access to hidden files except .well-known
location ~ /\.(?!well-known) {
    deny all;
}

# Restrict access to system and content files
location ~* ^/(site|kirby)/ {
    deny all;
}

location ~* ^/content/.*\.(txt|md|mdown)$ {
    deny all;
}

# Disable directory listing
autoindex off;

# Disable ETags
etag off;

# MIME and charset
include mime.types;
default_type application/octet-stream;

It does serve well the static website but when I go to /edition (to reach kirby panel), my web browser download /var/www/kirby/www/index.php file instead of running it.

Any idea how to make nginx work with static and kirby panel?

The reason your browser is downloading the PHP file instead of executing it is due to Location Block Inheritance.

In Nginx, once a request matches a “prefix” location (like your location ^~ /edition/), it stops looking for other matches unless specifically told otherwise. Since your PHP processing block is a separate top-level block, and you’ve used the ^~ modifier (which prevents regex matching), Nginx finds the file via alias but doesn’t know it should hand it off to the FastCGI socket.

To fix this, you need to nest the PHP handler inside the /edition/ block.

The Corrected Nginx Configuration

Here is the streamlined logic. Replace your location / and /edition/ blocks with this structure:

1. Main Static Site

location / {
root /var/www/kirby/static/;
index index.html;
try_files $uri $uri/ =404;
}

2. Kirby Panel (PHP App)

location ^~ /edition/ {
alias /var/www/kirby/www/;
index index.php;

# Basic Kirby routing within the alias
try_files $uri $uri/ /edition/index.php?$args;

# NESTED PHP processing specifically for the panel
location ~ \.php$ {
    # When using alias, we must manually set the SCRIPT_FILENAME
    fastcgi_pass unix:/var/run/php/php8.2-fpm-kirby.sock;
    include fastcgi_params;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    
    # This is the critical line for alias + php
    fastcgi_param SCRIPT_FILENAME /var/www/kirby/www/index.php;
    
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_param REMOTE_USER $remote_user;
}

}

3. Handle Kirby Assets (Media/Assets for the Panel)

Since your panel is at /edition, Kirby might try to load /media or /assets

if these aren’t in your static folder, you need to route them to the PHP source:

location ~ ^/(media|assets)/ {
root /var/www/kirby/www/;
try_files $uri $uri/ =404;
}

Key Adjustments Explained

  • Nested PHP Block: By putting the location ~ \.php$ block inside the location ^~ /edition/ block, you ensure that any PHP file found within the /edition/ path is actually executed.

  • The alias vs root Trap: Nginx’s $request_filename variable behaves inconsistently with alias. In the nested PHP block, I explicitly set SCRIPT_FILENAME to the absolute path of your Kirby index.php. This is the most reliable way to ensure the FastCGI server knows exactly which file to run.

  • The try_files Logic: In the static block (location /), we removed /index.php?$args because your static site shouldn’t need to fall back to a PHP index unless the static file is missing.

  • Media/Assets: Kirby’s Panel needs access to the media folder (to generate thumbnails) and the assets folder (for the Panel’s own CSS/JS). Since your root / points to the static folder, you must add a specific block (Point #3 above) to tell Nginx to look in the www folder for these specific directories.

A Quick Security Note

Since you are now serving a static site as the “face” of the web, make sure your /var/www/kirby/static/ directory does not contain a .env file or any Kirby system folders that the static generator might have accidentally copied over. Your existing security headers at the bottom of the config will still protect the /edition/ (www) path correctly.