Automated LNMP (Linux + Nginx + MySQL/MariaDB + PHP) environment builder for Ubuntu servers.
All software compiled from source or installed via official binaries — no third-party mirrors, no vendor lock-in.
- Ubuntu-only — Supports Ubuntu 26.04 LTS (recommended), 24.04 LTS, and 22.04 LTS
- Compile from source — Nginx with latest OpenSSL (HTTP/2, HTTP/3, TLS 1.3), PHP 8.4 with OPcache enabled and JIT disabled by default
- Auto-tuning — Nginx workers, file cache, MySQL InnoDB buffer pool, PHP-FPM children automatically calculated based on host CPU/RAM
- System hardening — TCP BBR, sysctl tuning, journald log limits, swap auto-creation
- Modern MIME types — Installation-managed Nginx
mime.typesincludes.mjs,.webmanifest,.wasm, source maps, JSON-LD, modern images, fonts, audio, and video - Timezone unified — Single
Timezonesetting applied across system, PHP, and MySQL - IP protection — Auto-generated catch-all server block prevents SSL certificate leakage via IP access
- PHP extension framework — Registry-based PECL extension compilation
- Batch extension install — Pre-configure extensions in config, built automatically during installation
- Tools included — Composer, WP-CLI, phpMyAdmin installed by default; Docker can be enabled in config
- SSL management — Let's Encrypt via acme.sh, self-signed certs, expiry monitoring
- Security hardening — SSH hardening, optional fail2ban/firewall, per-vhost open_basedir
- systemd native — All services managed via systemd with auto-restart on failure
- 100% official sources — Every download comes from nginx.org, php.net, cdn.mysql.com, etc.
- Non-interactive mode —
install.sh --auto,lnmp --yes, and env flags for fully automated deployment (cloud-init / Terraform ready)
| OS | Version | Status |
|---|---|---|
| Ubuntu | 26.04 LTS (Resolute) | ✅ Recommended |
| Ubuntu | 24.04 LTS (Noble) | ✅ Supported |
| Ubuntu | 22.04 LTS (Jammy) | ✅ Supported |
Full lnmp and db installs currently require x86_64 / amd64 because the
database engines are installed from official x86_64 binary tarballs. The
nginx target can run on aarch64, but database installation will stop with a
clear error instead of installing an incompatible binary.
| Software | Version | Install Method |
|---|---|---|
| Nginx | 1.30.4 | Compile (with OpenSSL 3.5.7) |
| MariaDB | 11.4.13 LTS | Binary (default) |
| MySQL | 8.4.11 LTS | Binary (not supported on Ubuntu 26.04) |
| PHP | 8.4.25 | Compile |
| Redis | 8.10.1 | Compile (optional) |
| Docker | Latest | Official script (get.docker.com) |
| Composer | Latest | Official installer (getcomposer.org) |
| WP-CLI | Latest | Official phar (wp-cli.org) |
| phpMyAdmin | 5.2.3 | Official tarball |
git clone https://github.com/nczz/lnmp-stack.git
cd lnmp-stack
# Create your local config (overrides lnmp.conf defaults)
cat > lnmp.conf.local << 'EOF'
Timezone='Asia/Taipei'
MySQL_Root_Password='your_secure_password'
PHP_Extensions_Install='redis imagick apcu'
EOF
# Install full stack
sudo ./install.sh lnmp
# Or non-interactive (cloud-init / Terraform ready)
sudo ./install.sh --auto lnmpNote: Do not edit
lnmp.confdirectly — it will be overwritten bygit pull. Always uselnmp.conf.localfor your customizations. It is gitignored and persists across updates.
All settings are documented with comments in lnmp.conf. Create lnmp.conf.local to override any value.
Load order: versions.conf → lnmp.conf (defaults) → lnmp.conf.local (your overrides)
| Parameter | Default | Description |
|---|---|---|
DB_Type |
mariadb |
Database engine: mariadb or mysql |
MySQL_Data_Dir |
/usr/local/mysql/var |
MySQL data directory |
MariaDB_Data_Dir |
/usr/local/mariadb/var |
MariaDB data directory |
MySQL_Root_Password |
(auto-generated) | Root password. If not set, a random one is generated and saved to .local |
On Ubuntu 26.04, DB_Type='mysql' is blocked by default because Oracle's
official MySQL 8.4 binary client tools still require libncurses.so.5 and
libtinfo.so.5, which are not available from Ubuntu 26.04 repositories. Use
the default DB_Type='mariadb' for Ubuntu 26.04. MySQL remains available for
older supported Ubuntu releases where its binary dependencies can be satisfied.
| Parameter | Default | Description |
|---|---|---|
Nginx_Modules_Options |
(empty) | Extra compile options, e.g. --add-module=/path |
Enable_Nginx_Openssl |
y |
Compile with latest OpenSSL (HTTP/2, HTTP/3, TLS 1.3) |
Enable_Nginx_Lua |
n |
Compile with Lua module (for WAF, custom logic) |
Memory_Allocator |
jemalloc |
Memory allocator: jemalloc or none |
| Parameter | Default | Description |
|---|---|---|
PHP_Modules_Options |
(empty) | Extra compile options, e.g. --with-pgsql |
Enable_PHP_Exif |
y |
Image EXIF metadata reading |
Enable_PHP_Fileinfo |
y |
File MIME type detection |
Enable_PHP_Ldap |
y |
LDAP directory access |
Enable_PHP_Bz2 |
y |
Bzip2 compression |
Enable_PHP_Sodium |
y |
Modern cryptography (libsodium) |
Enable_PHP_Imap |
n |
Legacy alias that queues the PECL imap extension |
PHP_Extensions_Install |
redis imagick apcu |
PECL extensions to compile after install |
PHP 8.4 moved IMAP out of PHP core and into PECL. Prefer
PHP_Extensions_Install='redis imagick apcu imap'; existing
Enable_PHP_Imap='y' overrides are treated as a compatibility alias and queue
the same PECL extension during full installs.
| Parameter | Default | Description |
|---|---|---|
Enable_Composer |
y |
PHP dependency manager |
Enable_Docker |
n |
Docker + Docker Compose |
Enable_WP_CLI |
y |
WordPress command-line tool |
Enable_phpMyAdmin |
y |
phpMyAdmin in default host (http://IP/phpmyadmin/) |
| Parameter | Default | Description |
|---|---|---|
Timezone |
Asia/Taipei |
Applied to system, PHP, and MySQL |
Default_Website_Dir |
/home/wwwroot/default |
Default website root |
Enable_Swap |
y |
Auto-create swap when RAM < 2GB |
Auto_Install |
n |
Skip prompts (set automatically by --auto flag) |
| Parameter | Default | Description |
|---|---|---|
Enable_Fail2ban |
n |
SSH + Nginx brute-force protection |
Firewall |
n |
ufw, iptables, or n (disabled). Firewall modes open IPv4/IPv6 SSH, HTTP, and HTTPS. |
Acme_Email |
(empty) | Email for Let's Encrypt/acme.sh registration. Optional (used only for expiry notices). Enables unattended SSL issuance. Can also be set via ACME_EMAIL env. |
Configs are automatically optimized based on server specs:
| Parameter | Formula |
|---|---|
Nginx worker_processes |
= CPU cores |
Nginx worker_connections |
= CPU cores × 1024 (max 65535) |
Nginx open_file_cache max |
50K ~ 900K based on RAM |
Nginx mime.types |
Project-managed modern MIME map deployed at install time |
MySQL innodb_buffer_pool_size |
= 50% of RAM (min 128M) |
MySQL max_connections |
64 ~ 512 based on RAM |
PHP-FPM pm.max_children |
= 30% of RAM ÷ 40MB per child |
PHP memory_limit |
64M ~ 512M based on RAM |
The installer automatically configures the host environment:
- TCP BBR — Enables BBR congestion control
- sysctl tuning — TCP fastopen, keepalive, port range, file limits, swappiness
- journald limits — 100M total, 20M per file to prevent log bloat
- ulimit — nofile/nproc set to 65535
- Swap — Auto-created when RAM < 2GB
- sudo NOPASSWD — Enabled for sudo group
- Timezone — System, PHP, and MySQL all set from single config value
sudo ./install.sh lnmp # Full stack: Nginx + MySQL + PHP
sudo ./install.sh nginx # Nginx only
sudo ./install.sh db # Database only
sudo ./install.sh --auto lnmp # Non-interactiveBash completion is installed automatically — press Tab to autocomplete commands and options.
lnmp start # Start all services
lnmp stop # Stop all services
lnmp restart # Restart all services
lnmp reload # Reload Nginx + PHP-FPM configs
lnmp status # Show service status with versions
lnmp kill # Force kill all LNMP processes# CLI mode (non-interactive)
lnmp vhost add example.com --rewrite wordpress --ssl --redirect
lnmp vhost add example.com --domains "www.example.com" --rewrite laravel --ssl
lnmp vhost del example.com
lnmp vhost list
# Interactive mode
lnmp vhost addOptions: --domains, --webroot, --rewrite, --ssl, --redirect
lnmp ssl install example.com # Issue Let's Encrypt certificate
lnmp ssl renew # Renew all certificates
lnmp ssl renew example.com # Renew specific domain
lnmp ssl revoke example.com # Revoke and remove certificate
lnmp ssl list # List certs with expiry (✅/⚠️/🔴)
lnmp ssl self example.com # Generate self-signed certificate- Uses Let's Encrypt as default CA (not ZeroSSL)
- EC-256 key type by default
- DH parameters auto-generated during install
- HTTPS catch-all with
ssl_reject_handshakeprevents cert leakage via IP
All vhost, ssl, and db subcommands are safe to drive from AI agents, CI,
cloud-init, or ssh host 'cmd' — they never hang on a prompt. The same
commands still prompt interactively when run from a real terminal, so human and
automated use share one code path.
See command-specific help (lnmp --help, lnmp vhost add --help,
lnmp ssl --help, lnmp ssl install --help, lnmp db --help, and
lnmp db add --help) plus AGENTS.md for the full operating
contract (SSL preconditions, exit codes, recipes).
# Force non-interactive mode (any one of these):
lnmp --yes vhost add site.com --rewrite wordpress --ssl --redirect
LNMP_ASSUME_YES=1 lnmp ssl install site.com
NONINTERACTIVE=1 lnmp db add mydb myuser --password-file /root/.lnmp-mydb.passBehavior in non-interactive mode:
- Missing or invalid required values exit with a sysexits code on
stderr instead of hanging:
64= bad/missing/invalid argument,69= dependency unavailable,75= transient (retry). - Optional values fall back to defaults. Supplied domains, webroots, rewrite names, and database identifiers are validated before root-owned files or SQL are touched.
- acme.sh registration email is read from
ACME_EMAILenv orAcme_Emailinlnmp.conf.local(email is optional for Let's Encrypt).
SSL preconditions (or issuance returns 75): DNS A/AAAA already points
here, port 80 reachable, vhost exists to serve /.well-known/acme-challenge/.
# CLI mode
install -m 600 /dev/null /root/.lnmp-mysite.pass
printf '%s\n' 'STRONG_PASSWORD_HERE' > /root/.lnmp-mysite.pass
lnmp db add mysite myuser --password-file /root/.lnmp-mysite.pass # Create database + user
lnmp db del mysite # Drop database + user
lnmp db list # List databases and users
lnmp db export mysite # Export to .sql.gz
lnmp db import mysite backup.sql.gz # Import (supports .gz)
# Interactive mode
lnmp db add
# Reset MySQL root password (reads from lnmp.conf.local)
lnmp reset-password# Pre-install via config
PHP_Extensions_Install='redis imagick apcu swoole'
# Or manage later
sudo lnmp addons install redis
sudo lnmp addons install imagick
sudo lnmp addons uninstall swoole
sudo lnmp addons list
# Available: redis, imagick, apcu, swoole, memcached, sodium, imap
# Standalone services
sudo lnmp addons install redis-server
sudo lnmp addons install memcached-serverOPcache is compiled and enabled by default. OPcache JIT is intentionally left
commented out in opcache.ini because it can trigger runtime bugs on some
workloads and PHP extension combinations. Uncomment opcache.jit_buffer_size
and opcache.jit only after validating the workload with JIT.
sudo ./tools/upgrade.sh nginx # Upgrade Nginx (edit versions.conf first)
sudo ./tools/upgrade.sh php # Upgrade PHP
sudo ./tools/backup.sh [dir] # Backup DB + sites + configs (7-day retention)
sudo ./tools/uninstall.sh # Uninstall (backup DB + preserve sites)
sudo ./tools/uninstall.sh --reset # Full reset for clean reinstall| Path | Description |
|---|---|
/usr/local/nginx/ |
Nginx installation |
/usr/local/nginx/conf/vhost/ |
Virtual host configs |
/usr/local/nginx/conf/ssl/ |
SSL certificates + DH parameters |
/usr/local/mysql/ |
MySQL installation |
/usr/local/mysql/var/ |
MySQL data (configurable) |
/usr/local/php/ |
PHP installation |
/usr/local/php/etc/php.d/ |
PHP extension .ini files |
/usr/local/redis/ |
Redis (if installed) |
/usr/local/acme.sh/ |
acme.sh (Let's Encrypt client) |
/home/wwwroot/ |
Website files |
/home/wwwlogs/ |
All logs (Nginx, PHP, FPM, mail) |
/root/.my.cnf |
MySQL root password (auto-generated) |
/root/lnmp-install.log |
Installation log |
MIT