Why Pterodactyl URLs break on shared hosting

Pterodactyl's panel is a Laravel 10 application, so every request that isn't a real file on disk has to be funneled into a single entry point: public/index.php. The routing table that turns /server/abc123/files or /admin/nodes into a controller lives entirely inside PHP. If the web server hands those paths straight to the filesystem instead of the front controller, you get a 404 from LiteSpeed itself rather than a styled Pterodactyl error page. That single behavior is behind almost every "friendly URL" complaint: the login page loads because it maps to the document root, but any deep link returns Not Found.

The second complication is document root placement. Pterodactyl expects the web server to serve from the public/ folder, not the repository root. On a managed account you usually can't move the account's docroot freely, so administrators either install the panel into a subfolder like public_html/panel or symlink public_html at the Laravel public/ directory. Each layout changes which .htaccess is read and what the RewriteBase needs to be. A rule set copied from a root installation will silently fail when the panel actually lives one directory deeper.

The third factor is the APP_URL value stored in the panel's environment. Laravel builds absolute URLs for assets, redirects, websocket endpoints, and OAuth callbacks from that string. When it says http:// but the visitor arrives over https://, browsers block the mixed content and the panel appears half-loaded with no CSS and a broken Wings console. None of that is a rewrite problem, yet it looks identical to one from the browser. Separating the rewrite layer from the canonical-URL layer is the key to diagnosing these issues quickly, and the sections below handle each in order.

Confirming the front controller and rewrite rules

Start by opening cPanel File Manager (or DirectAdmin File Manager) and navigating to the directory that is actually served to the browser. For a subfolder install that is typically /home/USER/public_html/panel/public, and for a docroot install it is /home/USER/public_html. Enable "Show Hidden Files (dotfiles)" from the File Manager settings so you can see .htaccess. Pterodactyl ships the correct file, but it is frequently lost during a manual upload or overwritten by a CMS installer. The canonical contents should match this:

<IfModule mod_rewrite.c>
    RewriteEngine On

    RewriteCond %{REQUEST_FILENAME} -d [OR]
    RewriteCond %{REQUEST_FILENAME} -f
    RewriteRule ^ ^$1 [N]

    RewriteCond %{REQUEST_URI} (\.\w+$) [NC]
    RewriteRule ^(.*)$ public/$1

    RewriteRule ^(.*)$ public/index.php
</IfModule>

That block belongs in the repository root when the account docroot points there without a dedicated public folder. If your docroot is the public/ directory itself, use Laravel's simpler public rule set instead:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    # Redirect trailing slashes if not a folder
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.+)/$
    RewriteRule ^ %1 [L,R=301]

    # Send everything to the front controller
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

LiteSpeed reads and honors mod_rewrite syntax natively, so these directives work without any Apache module being loaded. If the panel sits in a subdirectory and links resolve to the wrong path, set RewriteBase /panel/ so relative rewrites keep the prefix. After editing, there is no daemon to restart from an unprivileged account; LiteSpeed picks up .htaccess changes on the next request. To verify the rewrite is active, visit a deep link such as /admin while logged in. A styled Pterodactyl 404 means routing works and the path simply doesn't exist; a bare LiteSpeed 404 means the request never reached index.php, so recheck the file location and that "Show Hidden Files" didn't reveal a second stale .htaccess higher up the tree.

File permissions also masquerade as rewrite failures. Confirm .htaccess is 0644 and that storage/ and bootstrap/cache/ are writable (typically 0755) through the File Manager permissions dialog. A non-writable cache directory throws a 500, not a 404, but the symptom of "my URLs stopped working" is reported identically. Check storage/logs/laravel.log in the File Manager viewer to read the actual exception before assuming the rewrite layer is at fault.

HTTPS canonicals, APP_URL, and clean redirects

Once routing works, fix the canonical address. Open the panel's .env file in File Manager (it lives in the repository root, one level above public/) and confirm the scheme and host exactly match how users reach the panel:

APP_URL=https://panel.example.com
APP_ENV=production

After changing .env, Laravel keeps a compiled config cache that will ignore your edit. You cannot run php artisan config:cache without SSH, but you can safely delete the stale cache file through File Manager: remove bootstrap/cache/config.php if it exists. Laravel regenerates runtime config from .env on the next request, so the new APP_URL takes effect immediately. This single step resolves the majority of "assets load over http and get blocked" reports.

To enforce HTTPS at the edge and give search engines and browsers one canonical host, add a redirect above the rewrite block in the same .htaccess:

<IfModule mod_rewrite.c>
    RewriteEngine On

    # Force HTTPS
    RewriteCond %{HTTPS} !=on
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

    # Force the www / non-www choice (keep one canonical host)
    RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
    RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]
</IfModule>

Keep this ahead of the front-controller rules so the redirect fires before Laravel ever runs. Issue your SSL certificate through cPanel's "SSL/TLS Status" (AutoSSL) or DirectAdmin's "SSL Certificates" panel first; a 301-to-HTTPS rule in front of a missing certificate produces an endless redirect error in the browser. Because a Pterodactyl panel is a private application rather than a public content site, there is no meaningful sitemap.xml or crawlable index to generate — and you generally want the opposite. Add a public/robots.txt with User-agent: * and Disallow: / to keep the admin and server-management routes out of search results, which is the correct SEO posture for an operations control plane.

If you need PHP settings adjusted for the redirects and websocket proxying to behave, use cPanel's MultiPHP INI Editor or a .user.ini in the docroot rather than editing any server-wide file — for example raising memory_limit or confirming display_errors is Off in production. The approach to running Laravel's queue and scheduler without a privileged daemon mirrors what we covered for another Laravel app in our BookStack cron and queue worker guide, and the same cron-driven pattern keeps Pterodactyl's scheduled tasks alive on an unprivileged account.

Verifying and locking in the configuration

Work through a short checklist to confirm every layer. Load the login page over HTTPS and inspect the browser console for mixed-content warnings; a clean console confirms APP_URL and the config cache are correct. Click into /admin/nodes and /server/UUID/files to prove deep routes resolve to styled pages rather than LiteSpeed 404s. Type the bare http:// and www variants of your host and confirm both land on a single canonical HTTPS address with a 301. Finally, open storage/logs/laravel.log after the tests and verify no new RouteNotFoundException or UrlGenerationException entries appeared. If a redirect loop surfaces, it is almost always the HTTPS rule firing while the certificate is still provisioning or a proxy terminating TLS upstream — recheck AutoSSL status before touching the rules again. Keep a copy of the working .htaccess and .env in the account's backups so a future panel upgrade that overwrites public/.htaccess can be restored in seconds without rebuilding the rewrite logic from memory.