phpMyAdmin HTTPS Configuration: Running the MySQL Admin Panel Securely Over TLS

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:

  1. Apache + mod_php (the default on many Linux distributions)
  2. 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

ProblemCauseFix
phpMyAdmin redirects to HTTP after loginForceSSL not set or proxy not sending X-Forwarded-ProtoSet $cfg['ForceSSL'] = true and add HTTPS on to fastcgi_param
Session cookie not marked SecureCookieSecure = falseSet $cfg['CookieSecure'] = true
502 Bad GatewayPHP-FPM not running or wrong socket pathCheck systemctl status php8.2-fpm and the socket path
Certificate warning in browserUsing HTTP port or self-signed cert not trustedUse Let’s Encrypt or install the CA on the client
403 Forbidden on phpMyAdmin pagesIP restriction blocking accessAdd 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.

Scroll to Top