Pterodactyl eggs for hosting PHP and static websites with nginx 1.30 and PHP 8.3 / 8.4 / 8.5 on PHP-FPM. There are two, pick one per server:
| Egg | File | For |
|---|---|---|
| Nginx Webhost | egg-nginx-webhost-<version>.json |
You upload the site yourself (SFTP or the file manager), or install WordPress |
| Nginx Webhost (Git) | egg-nginx-webhost-git-<version>.json |
The site is cloned from a Git repo (public or private), optionally pulled on every start |
Both use the same image, config files and startup script.
- WordPress (Nginx Webhost): turn on Setup · WordPress when creating the server and it's downloaded for you
- Composer: list packages in Setup · Composer Modules and they're installed into
webroot/ - Site variables: site name and URL, Google Analytics, SMTP / mail, Cloudflare Turnstile,
Discord and Google OAuth and a database connection string are passed to PHP, so your code
reads them with
getenv()instead of hardcoding them - Update check: the console says when the egg or the Docker image has an update
- Maintained images: Debian based with PHP from packages.sury.org,
amd64andarm64, rebuilt weekly for nginx and PHP security fixes
- Pterodactyl Panel 1.x with Wings (the egg is in the
PTDL_v2format) - An
amd64orarm64node - Internet access from the node during install: the installer downloads this egg's config files from GitHub, and WordPress or your Git repo if you use them
- Download
egg-nginx-webhost-<version>.json,egg-nginx-webhost-git-<version>.jsonor both from the latest release. - In the panel: Admin → Nests → Import Egg, upload each one into the nest you want.
- Create a server with the egg and pick a PHP version under Docker Image.
- Once it has installed, start it and visit
http://<node-ip>:<port>. You should see the "Web server is running" page (or your site, with the Git egg); with Nginx Webhost, replace it by uploading your site intowebroot/.
The server listens on its primary allocation. To serve a domain over HTTPS, put a reverse proxy (nginx, Caddy, Cloudflare, ...) in front of that port.
All variables can be changed later under the server's Startup tab. Each name starts with its section (Site, Git, Setup, Mail, Login, Security, Analytics, Database, Egg) so they stay grouped in the panel. The site variables only reach your PHP code; the egg itself doesn't use them, so leave the ones you don't need blank.
| Variable | Default | What it does |
|---|---|---|
Site · Root Folder (ROOT_FOLDER) |
Folder in webroot/ to serve at your domain's root, see below. Blank serves webroot/ as is |
|
Site · 404 Page (ERROR_404_PAGE) |
Page to show for missing pages, see below. Blank keeps sending them to index.php |
|
Setup · WordPress (WORDPRESS) |
0 |
Nginx Webhost only. Install WordPress into an empty webroot/. Finish setup at /wp-admin |
Setup · Composer Modules (COMPOSER_MODULES) |
Space separated packages, e.g. phpmailer/phpmailer vlucas/phpdotenv. Pin a version with vendor/package:^6.0 |
| Variable | Default | What it does |
|---|---|---|
Egg · Update Check (UPDATE_CHECK) |
1 |
Say in the console when the egg or the Docker image has an update |
Egg · Startup Command (STARTUP_CMD) |
./start.sh |
Leave as is unless you know you need to change it |
Each Composer package is added once and then kept at that version. On every start
composer install keeps vendor/ in line with composer.lock.
Keep your main site in its own folder (any name) and other sites next to it:
webroot/
├── <folder>/ -> example.com/
├── blog/ -> example.com/blog/
└── shop/ -> example.com/shop/
Set Root Folder to <folder> (the folder's name, e.g. site, or a path to a nested
folder, e.g. dev/main for webroot/dev/main) in the server's Startup tab and restart
the server. webroot/<folder> is then served at /, every other
folder in webroot/ is still served at its own path (webroot/blog at /blog/), and old
links to /<folder>/... redirect to the same page at /.... PHP and the dotfile blocking
work the same in all of them.
- Each part of the path can use letters, numbers,
.,_and-, and starts with a letter or number (no spaces, no..). - With a nested path like
dev/main, the other folders inwebroot/devare still served at/dev/<other>/. - If the main site and
webroot/both have a folder with the same name (e.g.<folder>/shopandshop/),webroot/shopwins. - Files at the top of
webroot/(not in a folder) aren't served. - Composer still installs into
webroot/, so from the main site load it withrequire __DIR__ . '/../vendor/autoload.php';. - If the folder doesn't exist the server serves
webroot/as is and says so in the console. - Servers installed before the egg had this option need a Reinstall first (see
Updates) to get the new
nginx/conf.d/default.conf.
Set 404 Page to the path of a page in your site, e.g. 404.html or errors/404.php, and
every page that doesn't exist shows it with a 404 status. HTML and PHP pages both work. The
path is from your site's root: webroot/, or the Root Folder if you set one.
- Blank (default) does what the egg always did: missing pages go to
index.php, which is what WordPress and other apps that route throughindex.phpneed. Leave it blank for those, they show their own 404 page. - Links in the page should start with
/(e.g./style.css), it's shown at whatever address was missing. - If the page doesn't exist, visitors get nginx's plain 404 and the console says so.
- Needs a Reinstall first on servers installed before the egg had this option.
| Variable | Default | What it does |
|---|---|---|
Git · Repo Address (GIT_ADDRESS) |
Repo to clone your site from, e.g. https://github.com/you/your-site |
|
Git · Branch (BRANCH) |
Branch to clone; blank uses the repo's default branch | |
Git · Username (USERNAME) |
For private repos | |
Git · Access Token (ACCESS_TOKEN) |
For private repos; use a personal access token, not your password | |
Git · Auto Update (AUTO_UPDATE) |
0 |
git pull on every start |
The repo is only cloned into an empty webroot/. If webroot/ is already a clone of the
same repo, a reinstall pulls it; any other files are left alone.
| Section | Variables | Default | Typical use |
|---|---|---|---|
| Site | SITE_NAME, SITE_URL |
Website, |
Your site's name and public URL (no trailing slash) |
SMTP_HOST, SMTP_PORT |
smtp.resend.com, 465 |
Mail server. For Gmail / Google Workspace use smtp.gmail.com and 587 |
|
SMTP_USERNAME, SMTP_PASSWORD |
resend, |
Mail login. For Resend, the password is your API key; for Gmail, an App Password | |
MAIL_FROM_EMAIL, MAIL_FROM_NAME |
Sender address and name | ||
MAIL_TO_EMAIL |
Where contact form mail goes | ||
| Login | DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET |
Discord OAuth2 login | |
| Login | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET |
Google OAuth2 login | |
| Security | TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY |
Cloudflare Turnstile bot check on forms | |
| Analytics | GA_MEASUREMENT_ID |
Google Analytics 4 Measurement ID, e.g. G-XXXXXXXXXX |
|
| Database | DB_CONNECTION_STRING |
Database URL, e.g. mysql://user:pass@host:3306/db |
<?php
$mail->Host = getenv('SMTP_HOST');
$mail->Port = (int) getenv('SMTP_PORT');
$mail->Username = getenv('SMTP_USERNAME');
$mail->Password = getenv('SMTP_PASSWORD');The images are ghcr.io/rustix-development/pterodactyl-nginx-egg:php8.3, :php8.4 and :php8.5,
built from docker/Dockerfile on Debian 13 with nginx, PHP-FPM (from packages.sury.org),
Composer 2 and Git.
PHP extensions: apcu, bcmath, ctype, curl, dom, exif, fileinfo, gd, gettext, gmp, iconv, imagick, intl, mbstring, mysqli, mysqlnd, opcache, openssl, pdo, pdo_mysql, pdo_pgsql, pdo_sqlite, pgsql, phar, posix, redis, session, simplexml, soap, sockets, sodium, sqlite3, tokenizer, xml, xmlreader, xmlwriter, zip.
| Path | What it's for |
|---|---|
webroot/ |
Your website. Upload your files here (index.html or index.php) |
php-fpm/php.ini |
PHP settings: memory limit (256M), upload size (100M), ... Restart to apply |
nginx/conf.d/default.conf |
The site's nginx config |
logs/ |
nginx and PHP logs. Any log over 10 MB is moved to *.log.1 |
start.sh |
Startup script |
Out of the box nginx refuses to serve dotfiles (.env, .git, ...), composer.json,
composer.lock, auth.json and PHP files under vendor/.
Reinstalling a server replaces nginx/, php-fpm/ and start.sh with fresh copies
(back up your edits to them first), pulls the Git repo if one is set, and installs WordPress
only into an empty webroot/. Your other website files are kept.
Every time a server starts it checks for updates in the background and says in the console if:
- the egg has an update: download
egg-nginx-webhost-<version>.json(oregg-nginx-webhost-git-<version>.jsonfor the Git egg) from the latest release, then Admin → Nests → (nest) → the egg → Update Egg with it (not Import Egg, that makes a second copy), and Reinstall the server. - the Docker image has an update: restart the server, the panel downloads the latest
image on every start. If it keeps showing, run
docker pull ghcr.io/rustix-development/pterodactyl-nginx-egg:php8.xon the node to see why. - the server still uses an image from the old address
ghcr.io/mkkbeydoun/pterodactyl-nginx-egg(servers created before v1.0.1): those images are no longer updated, and updating the egg doesn't change an existing server's image. In the server's Startup tab (or Admin → Servers → server → Startup), set Docker Image toghcr.io/rustix-development/pterodactyl-nginx-egg:php8.xwith the same PHP version, then restart. - the server's PHP version is no longer offered, or the server isn't using one of this egg's images: Admin → Servers → server → Startup → Docker Image, pick one of the egg's images.
Turn it off per server with the Egg · Update Check variable.
- nginx or PHP-FPM won't start: the console shows the error; the full logs are in
logs/. If you editednginx/conf.d/default.conf, the config test error points at the line. - Git clone fails: check the address, branch and, for a private repo, the username and
access token. The clone only runs into an empty
webroot/, so empty it and reinstall to switch repos. - Uploads are rejected: raise
upload_max_filesizeandpost_max_sizeinphp-fpm/php.iniandclient_max_body_sizeinnginx/conf.d/default.conftogether.
Found a bug or want a feature? Open an issue.
MIT, see LICENSE. Based on the WebHost egg by Sigma Productions, originally tenten8401/pterodactyl-nginx, with Git install logic from parkervcp/eggs; see NOTICE.md for credits and their licenses.