Home · Guides / 5 / Secure / 5.2
Guide 5.2
Harden a LEMP stack
LEMP is Linux, NGINX, MySQL (or MariaDB), and PHP. Hardening is a degree of resistance, not a finished state: keep packages current, shrink what is listening, and stop the web stack from guessing filenames or exposing version banners.
Linux host
-
Install updates and, on a long-lived host, unattended security upgrades.
sudo apt update sudo apt upgrade sudo apt install unattended-upgrades sudo dpkg-reconfigure --priority=low unattended-upgrades -
Do not SSH in as root. Use a sudo user and key authentication. Keep a second session open while you edit
/etc/ssh/sshd_config:PermitRootLogin no PasswordAuthentication nosudo systemctl reload ssh -
Firewall policy: deny by default, then allow only what you need. For an onion-only web server that is the SSH port (and nothing on 80/443 from the WAN, because NGINX should already bind localhost).
sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow OpenSSH sudo ufw enable sudo ss -nltpStop and disable daemons you do not use (mail, FTP, extra databases). RoseHosting’s original article stresses the same idea with
systemctl disable.
NGINX
-
Run the worker as
www-data(Ubuntu default), not root. In/etc/nginx/nginx.conf:user www-data; server_tokens off; -
Keep the site on localhost, as in guide 4. Do not add a public
listen 80“for convenience”. -
Onion connections are already encrypted inside Tor. A clearnet TLS redirect (the usual
return 301 https://…pair in the RoseHosting article) only matters if you also publish a public hostname. Skip it for a pure .onion vhost. -
Do not send every
*.phppath to PHP-FPM blindly. Require the file to exist, and refuse scripts under upload directories:location ~ \.php$ { try_files $uri =404; include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.1-fpm.sock; } location /uploads { location ~ \.php$ { return 403; } }Point
fastcgi_passat the PHP version you actually installed.
MySQL
This repeats guide 4.2 because it is the most important database control: nothing but localhost should reach the engine, and each site should have its own user, not root.
bind-address = 127.0.0.1
sudo mysql_secure_installation
PHP
-
Set
cgi.fix_pathinfo=0in the FPMphp.ini(already covered in guide 4.1) so PHP does not execute a neighbour file when the requested path is missing. -
Hide the PHP banner and cap resource use. In the FPM ini:
expose_php = Off max_execution_time = 30 max_input_time = 30 memory_limit = 128M post_max_size = 8MIf the application never accepts uploads, set
file_uploads = Off. If it does, keepupload_max_filesizeonly as large as the app needs. -
Disable unused extensions (
php -m) rather than leaving every module loaded. Restart FPM after ini changes:sudo service php7.1-fpm restart