Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.websitestudio.app/llms.txt

Use this file to discover all available pages before exploring further.

VoxelSite ships with an .htaccess file that handles clean URL routing and security rules on Apache. Nginx ignores .htaccess entirely — you need to add equivalent rules to your Nginx site configuration. Without this configuration, visiting /about will show your homepage instead of about.php, because Nginx doesn’t know to try the .php extension.

The essential fix: clean URLs

VoxelSite generates pages as PHP files (about.php, contact.php, etc.) and links to them with clean URLs (/about, /contact). On Apache, the .htaccess rewrite rule handles this automatically. On Nginx, you need to add a named location that rewrites extensionless URLs to .php. Find the location / block in your Nginx config and replace it with:
# Clean URLs: /about → /about.php (processed by PHP)
location / {
    try_files $uri $uri/ @cleanurls;
}

location @cleanurls {
    rewrite ^/(.+)$ /$1.php last;
}
Why not $uri.php in try_files? Adding $uri.php as a middle argument in try_files finds the file but serves it as raw text — it bypasses the PHP handler. The named @cleanurls location with rewrite ... last does a proper internal redirect that re-evaluates against location ~ \.php$, ensuring PHP-FPM processes the file.

Agent API routing

The Agent API works without this rule. The Studio router automatically detects /agent/v1/ requests and forwards them to the Agent API router. The Nginx rewrite below is a performance optimization — it routes requests directly to the Agent API router, skipping the Studio router overhead. For most installations, the automatic fallback is sufficient.
If you want direct routing (recommended for production), add this before the location / block:
# Agent API: route all /_studio/api/agent/v1/* to the router
# The negative lookahead (?!.*\.php) prevents a rewrite loop —
# without it, the rewritten router.php URI matches this block again.
location ~ ^/_studio/api/agent/v1/(?!.*\.php)(.*)$ {
    rewrite ^/_studio/api/agent/v1/(.*)$ /_studio/api/agent/v1/router.php?_path=$1&$args last;
}
This mirrors the Apache .htaccess rule, which passes the path as ?_path=$1. For example, a request to /_studio/api/agent/v1/pages will be rewritten to /_studio/api/agent/v1/router.php?_path=pages. The .htaccess also blocks access to sensitive directories and files. Add these before the location / block:
# Block sensitive directories
location ~ ^/(\.ai|\.git|_data|scripts|vendor|node_modules|docs)/ {
    deny all;
    return 403;
}

# Block sensitive root files
location ~ ^/(composer\.(json|lock)|package(-lock)?\.json)$ {
    deny all;
    return 403;
}

# Block form definition files (contain spam protection config)
location ~ ^/assets/forms/.*\.json$ {
    deny all;
    return 403;
}

# Block sensitive file types
location ~ \.(db|sqlite|sqlite3|sql|sh|env|bak|log)$ {
    deny all;
    return 403;
}

Complete Nginx config example (Forge)

Here’s a complete site configuration block for Laravel Forge. Replace the site ID (3050636) with your own:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_dhparam /etc/nginx/dhparams.pem;

add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";

index index.html index.htm index.php;

charset utf-8;

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/YOUR_SITE_ID/server/*;

# ── Security blocks ──

location ~ ^/(\.ai|\.git|_data|scripts|vendor|node_modules|docs)/ {
    deny all;
    return 403;
}

location ~ ^/(composer\.(json|lock)|package(-lock)?\.json)$ {
    deny all;
    return 403;
}

location ~ ^/assets/forms/.*\.json$ {
    deny all;
    return 403;
}

location ~ \.(db|sqlite|sqlite3|sql|sh|env|bak|log)$ {
    deny all;
    return 403;
}

# ── Agent API routing ──

location ~ ^/_studio/api/agent/v1/(?!.*\.php)(.*)$ {
    rewrite ^/_studio/api/agent/v1/(.*)$ /_studio/api/agent/v1/router.php?_path=$1&$args last;
}

# ── Clean URL routing ──

location / {
    try_files $uri $uri/ @cleanurls;
}

location @cleanurls {
    rewrite ^/(.+)$ /$1.php last;
}

location = /favicon.ico { access_log off; log_not_found off; }
location = /favicon.svg { access_log off; log_not_found off; }
location = /robots.txt  { access_log off; log_not_found off; }

access_log off;
error_log  /var/log/nginx/YOUR_SITE_ID-error.log error;

error_page 404 /index.php;

location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    include forge_fastcgi_defaults;

    # Forward the Authorization header to PHP (required for Agent API Bearer tokens)
    fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}

location ~ /\.(?!well-known).* {
    deny all;
}
The fastcgi_param HTTP_AUTHORIZATION $http_authorization; line is essential if you use the Agent API. Without it, Nginx strips the Authorization header before it reaches PHP, causing all authenticated API calls to fail with 401.

Where to edit this on Forge

1

Open the Nginx configuration editor

Go to Sites → your site → Nginx Configuration (the Edit button on the files panel).
2

Locate the server block

Find the server { ... } block in your config.
3

Apply the changes

Replace the location / block and add the security rules as shown above.
4

Save

Click Save — Forge automatically reloads Nginx.