phpMyAdmin is one of the most targeted web applications on the internet — attack scanners look for it at dozens of common URL paths, and if they find it running over HTTP, they can harvest MySQL credentials from network traffic or from browser history in shared environments. This article covers deploying phpMyAdmin behind nginx with TLS, restricting access, and setting the correct session cookie security flags.
phpMyAdmin is always behind a web server
phpMyAdmin is a PHP application. It does not run its own HTTP server — it runs behind Apache, nginx, or another web server. This means TLS is configured at the web server level, not inside phpMyAdmin. The two common setups are:
- Apache + mod_php (the default on many Linux distributions)
- nginx + PHP-FPM (recommended for production)
Both are covered below.
Option A: TLS with Apache
Install the SSL module and enable it
sudo a2enmod ssl
sudo a2enmod rewrite
sudo systemctl reload apache2
Create the virtual host
sudo nano /etc/apache2/sites-available/phpmyadmin-ssl.conf
<VirtualHost *:80>
ServerName phpmyadmin.example.com
RewriteEngine On
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
</VirtualHost>
<VirtualHost *:443>
ServerName phpmyadmin.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/phpmyadmin.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/phpmyadmin.example.com/privkey.pem
SSLProtocol -All +TLSv1.2 +TLSv1.3
SSLCipherSuite HIGH:!aNULL:!MD5:!RC4
SSLHonorCipherOrder On
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Frame-Options SAMEORIGIN
Header always set X-Content-Type-Options nosniff
# phpMyAdmin root
DocumentRoot /usr/share/phpmyadmin
<Directory /usr/share/phpmyadmin>
Options SymLinksIfOwnerMatch
DirectoryIndex index.php
AllowOverride All
Require all granted
</Directory>
# Block direct access to sensitive phpMyAdmin directories
<Directory /usr/share/phpmyadmin/setup>
Require all denied
</Directory>
<Directory /usr/share/phpmyadmin/libraries>
Require all denied
</Directory>
<Directory /usr/share/phpmyadmin/templates>
Require all denied
</Directory>
ErrorLog ${APACHE_LOG_DIR}/phpmyadmin_error.log
CustomLog ${APACHE_LOG_DIR}/phpmyadmin_access.log combined
</VirtualHost>
Enable the site and restart:
sudo a2ensite phpmyadmin-ssl.conf
sudo a2dissite 000-default.conf # disable default if not needed
sudo apache2ctl configtest && sudo systemctl reload apache2
Option B: TLS with nginx + PHP-FPM (recommended)
Install nginx and PHP-FPM
sudo apt install nginx php-fpm php-mbstring php-zip php-gd php-json php-curl
Configure PHP-FPM
PHP-FPM listens on a Unix socket by default. Find the socket path:
ls /var/run/php/
# php8.2-fpm.sock
Create the nginx virtual host
sudo nano /etc/nginx/sites-available/phpmyadmin
server {
listen 80;
server_name phpmyadmin.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name phpmyadmin.example.com;
root /usr/share/phpmyadmin;
index index.php;
ssl_certificate /etc/letsencrypt/live/phpmyadmin.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/phpmyadmin.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Restrict to trusted IP ranges
# Uncomment and update to lock down access:
# allow 10.0.0.0/8;
# allow 192.168.0.0/16;
# deny all;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS on;
}
# Block access to sensitive directories
location ~ ^/(setup|libraries|templates|locale)/ {
return 403;
}
# Block dot files
location ~ /\. {
return 404;
}
access_log /var/log/nginx/phpmyadmin_access.log;
error_log /var/log/nginx/phpmyadmin_error.log;
}
Enable and reload:
sudo ln -s /etc/nginx/sites-available/phpmyadmin /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Configuring phpMyAdmin to enforce HTTPS in its own settings
phpMyAdmin has its own session cookie settings in config.inc.php. Open it:
sudo nano /etc/phpmyadmin/config.inc.php
# or
sudo nano /usr/share/phpmyadmin/config.inc.php
Add or update:
/* Force cookie-based authentication */
$cfg['Servers'][$i]['auth_type'] = 'cookie';
/* Secure the session cookie */
$cfg['CookieSecure'] = true; // Only send cookie over HTTPS
$cfg['CookieSameSite'] = 'Lax'; // CSRF protection
/* Require HTTPS for the admin interface */
$cfg['ForceSSL'] = true;
/* Hide phpMyAdmin from direct discovery */
$cfg['ShowAll'] = false;
/* Limit login attempts */
$cfg['LoginCookieValidity'] = 1440; // 24 minutes
$cfg['LoginCookieRecall'] = false; // Do not remember login
The ForceSSL = true setting causes phpMyAdmin to redirect HTTP requests to HTTPS internally.
Obtaining a certificate with Certbot
Standalone (when no web server is running yet)
certbot certonly --standalone -d phpmyadmin.example.com
Webroot (when nginx or Apache is already running)
certbot certonly --webroot \
-w /usr/share/phpmyadmin \
-d phpmyadmin.example.com
Apache plugin (automatic Apache configuration)
certbot --apache -d phpmyadmin.example.com
Nginx plugin (automatic nginx configuration)
certbot --nginx -d phpmyadmin.example.com
Automating certificate renewal
Certbot’s systemd timer handles renewal automatically. For nginx, add a deploy hook:
sudo nano /etc/letsencrypt/renewal-hooks/deploy/nginx-phpmyadmin.sh
#!/bin/bash
systemctl reload nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx-phpmyadmin.sh
Test the renewal process:
sudo certbot renew --dry-run
Restricting phpMyAdmin access by IP
Exposing phpMyAdmin to the entire internet is dangerous. Even with TLS and strong passwords, it is a target for credential stuffing and brute force. Restrict access to trusted IP ranges in nginx:
location / {
allow 10.0.0.0/8;
allow 192.168.1.0/24;
allow 203.0.113.0/24; # your office IP
deny all;
try_files $uri $uri/ /index.php?$query_string;
}
Or use HTTP basic authentication as an additional layer:
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/phpmyadmin_htpasswd admin
location / {
auth_basic "phpMyAdmin";
auth_basic_user_file /etc/nginx/phpmyadmin_htpasswd;
try_files $uri $uri/ /index.php?$query_string;
}
Changing the phpMyAdmin URL path
Moving phpMyAdmin to a non-default URL path is not security by obscurity — it is just a way to reduce automated scan noise. It is not a substitute for IP restrictions and strong authentication.
In nginx, change the root path:
location /db-manager/ {
alias /usr/share/phpmyadmin/;
index index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
}
}
Update phpMyAdmin’s config.inc.php:
$cfg['PmaAbsoluteUri'] = 'https://phpmyadmin.example.com/db-manager/';
Verifying TLS is working
# SSL Labs grade check (from outside)
curl -s "https://api.ssllabs.com/api/v3/analyze?host=phpmyadmin.example.com&publish=off&ignoreMismatch=on" | python3 -m json.tool | grep -A2 "grade"
# Quick openssl test
openssl s_client -connect phpmyadmin.example.com:443 -servername phpmyadmin.example.com </dev/null 2>&1 \
| grep -E "(subject=|issuer=|notAfter=|Protocol|Cipher)"
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| phpMyAdmin redirects to HTTP after login | ForceSSL not set or proxy not sending X-Forwarded-Proto | Set $cfg['ForceSSL'] = true and add HTTPS on to fastcgi_param |
| Session cookie not marked Secure | CookieSecure = false | Set $cfg['CookieSecure'] = true |
| 502 Bad Gateway | PHP-FPM not running or wrong socket path | Check systemctl status php8.2-fpm and the socket path |
| Certificate warning in browser | Using HTTP port or self-signed cert not trusted | Use Let’s Encrypt or install the CA on the client |
403 Forbidden on phpMyAdmin pages | IP restriction blocking access | Add your IP to the allow list in nginx |
Summary
phpMyAdmin TLS is entirely a web server configuration task. Use nginx or Apache to terminate TLS, redirect HTTP to HTTPS, and set security headers. In phpMyAdmin’s config.inc.php, set CookieSecure = true and ForceSSL = true to prevent session cookies from being sent over HTTP. Block access to phpMyAdmin’s setup and libraries directories. Restrict access by IP in nginx to reduce exposure — TLS alone does not prevent brute force attacks.