Blog/How to Install and Secure phpMyAdmin with Nginx on Ubuntu (Custom URL, Basic Auth, and No Root Login)

NginxphpMyAdminMySQLPHPSecurityDevOps··greencrew

How to Install and Secure phpMyAdmin with Nginx on Ubuntu (Custom URL, Basic Auth, and No Root Login)

To run phpMyAdmin with Nginx on Ubuntu, install it with sudo apt install phpmyadmin, choose no web server when the installer asks for Apache or lighttpd, and serve /usr/share/phpmyadmin from an Nginx location block on a hard-to-guess path that passes PHP to PHP-FPM. Then lock it down with HTTPS, HTTP basic auth, an IP allowlist, and a dedicated MySQL user instead of root. This guide covers the full config, every security layer, and fixes for common phpMyAdmin errors.

Short answer: Run sudo apt install phpmyadmin. When the installer asks which web server to configure, select neither Apache nor lighttpd, and answer Yes to setting up the database with dbconfig-common. phpMyAdmin installs into /usr/share/phpmyadmin. Add a location ^~ /your-secret-path/ block to your Nginx server block that uses alias /usr/share/phpmyadmin/; and passes .php files to your PHP-FPM socket. Then secure it: serve it over HTTPS only, add auth_basic with an htpasswd file, restrict access to your IP with allow/deny, and log in with a dedicated MySQL user rather than root. Reload Nginx with sudo nginx -t && sudo systemctl reload nginx.

What Is phpMyAdmin?

phpMyAdmin is a web-based interface for managing MySQL and MariaDB. You can browse tables, run SQL, manage users, and import and export dumps from the browser. It's a PHP application, so on an Nginx server it runs exactly like any other PHP site: Nginx serves its static files and hands .php requests to PHP-FPM.

It's also one of the most scanned paths on the internet. Bots constantly request /phpmyadmin, /pma, and /phpMyAdmin looking for weak passwords and old versions. A default install on a public server is an open door to your database. The security steps in this guide aren't optional extras. They're what makes phpMyAdmin safe to run at all.

Security layerWhat it stopsStep
Custom URL pathAutomated scanners hitting /phpmyadminStep 3
HTTPSPasswords and data sent in plain textStep 4
HTTP basic authAnyone reaching the phpMyAdmin login pageStep 5
IP allowlistEveryone except your own addressesStep 6
Dedicated MySQL user, root login disabledAttacks on the most powerful accountSteps 2 and 4
SSH tunnel (alternative)All public exposureStep 8

Prerequisites

  • Ubuntu 24.04 or 22.04 with a sudo user
  • Nginx, MySQL (or MariaDB), and PHP-FPM installed. If you need them, follow how to install a LEMP stack on Ubuntu.
  • A domain pointing to the server, for HTTPS

This guide uses the PHP 8.3 socket /run/php/php8.3-fpm.sock (the Ubuntu 24.04 default). Run ls /run/php/ and use your own socket name.

Step 1: Install phpMyAdmin with apt

sudo apt update
sudo apt install phpmyadmin php8.3-mbstring php8.3-zip php8.3-gd php8.3-curl

The installer asks two questions:

PromptWhat to chooseWhy
"Web server to reconfigure automatically" (apache2 / lighttpd)Select neither. Press Tab to reach <Ok> and press Enter without checking a boxYou're configuring Nginx yourself. Checking apache2 can install and start Apache on port 80
"Configure database for phpmyadmin with dbconfig-common?"Yes, then set a strong passwordCreates the phpmyadmin control user and database used for bookmarks, history, and other features

If the password step fails with ERROR 1819 (HY000): Your password does not satisfy the current policy requirements, MySQL's validate_password component rejected it. Choose retry and enter a longer password that mixes upper case, lower case, numbers, and symbols.

Restart PHP-FPM so it loads the new extensions:

sudo systemctl restart php8.3-fpm

Where phpMyAdmin's Files Live

PathContents
/usr/share/phpmyadmin/Application code (the web root you'll point Nginx at)
/etc/phpmyadmin/config.inc.phpMain config, managed by the package
/etc/phpmyadmin/conf.d/Your own override files (*.php), loaded by the main config
/etc/phpmyadmin/config-db.phpControl-user credentials generated by dbconfig-common
/var/lib/phpmyadmin/tmp/Temp and cache directory, must be writable by www-data

Ubuntu's package can lag behind the newest phpMyAdmin release. It gets security fixes through Ubuntu's normal updates, so keep running sudo apt upgrade. If you need a newer release, you can download it from phpmyadmin.net/downloads instead, but then you're responsible for updating it yourself.

Step 2: Create a Dedicated MySQL User for phpMyAdmin

On Ubuntu, MySQL's root account uses the auth_socket plugin by default. It only accepts logins from the system's root user over the local socket, so logging in to phpMyAdmin as root fails with #1698 - Access denied for user 'root'@'localhost'. Leave it that way. Create a separate account for web logins:

sudo mysql
CREATE USER 'dbadmin'@'localhost' IDENTIFIED BY 'a-long-unique-password';

-- Full admin access (only if you really need it):
GRANT ALL PRIVILEGES ON *.* TO 'dbadmin'@'localhost' WITH GRANT OPTION;

-- Or, better: access to one application database only
-- GRANT ALL PRIVILEGES ON myapp.* TO 'dbadmin'@'localhost';

FLUSH PRIVILEGES;
EXIT;

Granting only the databases a person actually manages limits the damage if the password leaks. MySQL's account management docs cover all the options.

Step 3: Configure Nginx to Serve phpMyAdmin on a Custom Path

Don't expose phpMyAdmin at /phpmyadmin. Pick a path that's hard to guess, such as /dbadmin-7f3a, and add this inside your site's existing server block (for example, /etc/nginx/sites-available/example.com):

# phpMyAdmin on a non-default path
location = /dbadmin-7f3a {
    return 301 /dbadmin-7f3a/;
}

location ^~ /dbadmin-7f3a/ {
    alias /usr/share/phpmyadmin/;
    index index.php;

    # Block internal directories (must come before the PHP block,
    # because Nginx uses the first regex location that matches)
    location ~ ^/dbadmin-7f3a/(libraries|setup|templates|vendor)/ {
        deny all;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

The details matter here:

DirectiveWhy it's written this way
location ^~ /dbadmin-7f3a/^~ stops Nginx from checking your site's other regex locations (like the main location ~ \.php$), so phpMyAdmin's PHP files are handled by this block only
alias /usr/share/phpmyadmin/;Maps /dbadmin-7f3a/ to the phpMyAdmin directory. Keep the trailing slash on both the location and the alias
SCRIPT_FILENAME $request_filenameWith alias, $request_filename holds the real file path. Using $document_root$fastcgi_script_name here gives "File not found"
include fastcgi_paramsUbuntu's snippets/fastcgi-php.conf uses try_files with $fastcgi_script_name, which doesn't work reliably with alias
deny all on setup/, libraries/The setup script and internal directories should never be reachable from the web

The alias directive docs explain how Nginx maps the path.

Test and reload:

sudo nginx -t && sudo systemctl reload nginx

Open https://example.com/dbadmin-7f3a/. You should see the phpMyAdmin login page.

Alternative: a separate subdomain. If you'd rather not mix phpMyAdmin into your app's server block, give it its own server block, such as db.example.com, with root /usr/share/phpmyadmin; and a normal location ~ \.php$ block. A subdomain is easier to firewall or switch off on its own, but it shows up in public certificate transparency logs, so use a name that doesn't scream "database".

Step 4: Serve phpMyAdmin Over HTTPS Only

phpMyAdmin sends your MySQL password and query results with every request. Never use it over plain HTTP. If the site doesn't have a certificate yet:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com

Certbot adds the certificate and an HTTP-to-HTTPS redirect to the server block. The full walkthrough is in how to install Let's Encrypt SSL on Nginx with Certbot. Then add your own security settings in an override file:

sudo nano /etc/phpmyadmin/conf.d/security.php
<?php
$cfg['Servers'][1]['AllowRoot'] = false;
$cfg['LoginCookieValidity'] = 1800; // log out after 30 minutes idle

AllowRoot = false blocks the root MySQL account from logging in through phpMyAdmin, even if someone later changes root to password authentication. The phpMyAdmin configuration docs list every $cfg option.

Step 5: Add HTTP Basic Authentication in Nginx

Basic auth puts a second password prompt in front of phpMyAdmin, so bots never reach the login form or its code. Create a password file:

sudo apt install apache2-utils     # provides htpasswd only, not the Apache server
sudo htpasswd -c /etc/nginx/.htpasswd-pma yourname
sudo chown root:www-data /etc/nginx/.htpasswd-pma
sudo chmod 640 /etc/nginx/.htpasswd-pma

Leave out -c when you add more users later, because -c overwrites the file. Then add two lines to the phpMyAdmin location:

location ^~ /dbadmin-7f3a/ {
    alias /usr/share/phpmyadmin/;
    index index.php;

    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd-pma;

    location ~ ^/dbadmin-7f3a/(libraries|setup|templates|vendor)/ {
        deny all;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

Nested locations inherit auth_basic, so the PHP files are protected too. Use a username and password that differ from your MySQL credentials. The directives are documented in ngx_http_auth_basic_module.

Step 6: Restrict phpMyAdmin to Your IP Address

If you connect from a fixed IP address, or through a VPN, an allowlist is the strongest single control you can add, because everyone else gets 403 Forbidden:

location ^~ /dbadmin-7f3a/ {
    alias /usr/share/phpmyadmin/;
    index index.php;

    satisfy all;              # require BOTH the IP match and basic auth
    allow 203.0.113.10;       # office
    allow 198.51.100.0/24;    # VPN range
    deny  all;

    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd-pma;

    location ~ ^/dbadmin-7f3a/(libraries|setup|templates|vendor)/ {
        deny all;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

satisfy all is the default, but writing it out makes the intent clear. satisfy any would let in anyone who passes either check. If your site sits behind Cloudflare or a load balancer, Nginx sees the proxy's IP instead of yours. Configure the realip module first, or the allowlist will either block everyone or allow everyone.

Reload after every change:

sudo nginx -t && sudo systemctl reload nginx

You can also rate-limit the login page to slow down brute-force attempts. See how to set up rate limiting in Nginx.

Step 7: Fix Uploads and Import Size Limits

Importing a large .sql dump fails until both Nginx and PHP allow the file size. Raise the limits together:

# In the phpMyAdmin location or the server block
client_max_body_size 64m;
; /etc/php/8.3/fpm/php.ini
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300
sudo systemctl restart php8.3-fpm
sudo nginx -t && sudo systemctl reload nginx

For very large databases, skip the browser and import from the shell instead: mysql -u dbadmin -p myapp < dump.sql.

Step 8: The Most Secure Option — Use an SSH Tunnel Instead

If you're the only person who uses phpMyAdmin, you don't need it on the public internet at all. Bind it to localhost and reach it over SSH:

# /etc/nginx/sites-available/phpmyadmin-local
server {
    listen 127.0.0.1:8081;
    root /usr/share/phpmyadmin;
    index index.php;

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}
sudo ln -s /etc/nginx/sites-available/phpmyadmin-local /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# On your own computer:
ssh -L 8081:127.0.0.1:8081 user@your-server

Then open http://localhost:8081 on your computer. Nothing is exposed publicly, and SSH encrypts the traffic. Remove the public location block from Step 3 if you go this route.

Troubleshooting phpMyAdmin on Nginx

SymptomCauseFix
#1698 - Access denied for user 'root'@'localhost'MySQL root uses auth_socketLog in with the dedicated user from Step 2
404 on /dbadmin-7f3a/alias path or trailing slash wrong, or block in the wrong server blockUse location ^~ /path/ with alias /usr/share/phpmyadmin/;, both with trailing slashes
File not found. or Primary script unknownSCRIPT_FILENAME built from $document_root with aliasUse fastcgi_param SCRIPT_FILENAME $request_filename;
PHP file downloads instead of runningRequest matched a location without fastcgi_passMake sure the nested location ~ \.php$ is inside the ^~ block
502 Bad GatewayWrong PHP-FPM socket, or PHP-FPM stoppedls /run/php/, sudo systemctl status php8.3-fpm
The mbstring extension is missingExtension not installed for the PHP version FPM runssudo apt install php8.3-mbstring && sudo systemctl restart php8.3-fpm
$cfg['TempDir'] (/var/lib/phpmyadmin/tmp) is not accessibleWrong ownershipsudo chown -R www-data:www-data /var/lib/phpmyadmin/tmp
Error during session startPHP session directory not writableCheck /var/lib/php/sessions permissions and the PHP-FPM error log
413 Request Entity Too Large on importNginx body limitRaise client_max_body_size, upload_max_filesize, and post_max_size (Step 7)
403 Forbidden for you tooYour IP isn't in the allowlist, or Nginx sees a proxy IPCheck /var/log/nginx/access.log for the IP Nginx sees and fix allow or the realip setup

The full PHP-FPM setup and its common errors are covered in how to configure PHP-FPM with Nginx and how to fix 502 Bad Gateway in Nginx.

Keep an Eye on the Site Around Your Database

phpMyAdmin makes it easy to run a heavy query, drop the wrong index, or lock a busy table, and the first sign is often your main site slowing down or returning errors. It's also worth knowing right away if the server itself goes down while you're working on it.

GreenCrew monitors your site over HTTP from outside the server and alerts you on Discord, Slack, or Telegram the moment it stops responding. It also gives you a public status page for your users. Point the check at a page that queries the database, not a static file, so a locked or crashed MySQL counts as downtime. Setup is in how to get website downtime alerts on Discord, Slack, and Telegram. Don't add a public uptime check for the phpMyAdmin URL itself. It should be blocked to everyone except you.

You may also want to add security headers in Nginx to the server block, such as X-Frame-Options, to protect the admin interface from clickjacking.

Frequently Asked Questions

Is phpMyAdmin safe to use on a production server?

It can be, if you keep it updated and don't leave it publicly open. Run it over HTTPS on a non-default path, behind basic auth and an IP allowlist, with root login disabled. The safest setup is to bind it to localhost and use an SSH tunnel, so it isn't reachable from the internet at all.

Why does phpMyAdmin say "Access denied for user root"?

On Ubuntu, MySQL's root account authenticates with auth_socket, which ignores passwords and accepts only the system root user over the local socket. phpMyAdmin connects as a normal client, so root login fails. Create a dedicated MySQL user instead of changing root's authentication method.

Can I install phpMyAdmin without Apache?

Yes. When apt install phpmyadmin asks which web server to configure, leave both apache2 and lighttpd unchecked. The package only installs PHP files into /usr/share/phpmyadmin, and you serve them with Nginx and PHP-FPM yourself.

How do I change the phpMyAdmin URL in Nginx?

Change the path in the location block, for example from /dbadmin-7f3a/ to /something-else/, keeping the alias /usr/share/phpmyadmin/; line and the trailing slashes. Update the paths in any nested deny rules too, then run sudo nginx -t && sudo systemctl reload nginx.

Conclusion

Installing phpMyAdmin with Nginx on Ubuntu takes one apt install and one location ^~ block with alias and fastcgi_param SCRIPT_FILENAME $request_filename. Securing it takes a few more steps: a custom path, HTTPS, basic auth, an IP allowlist, a dedicated MySQL user, and AllowRoot = false. If only you need access, bind it to localhost and use an SSH tunnel. Finally, add a GreenCrew check on your main site so you hear about it right away if a database change takes the app down.