Install MySQL on a Windows Server VPS
To install MySQL on a Windows Server VPS, Oracle provides an MSI package that installs the server and MySQL Configurator, the setup tool. This guide follows that official method, with the GUI or from the command line, then opens remote access, tunes performance, secures and backs up MySQL. MySQL Installer, still mentioned in old tutorials, stops at the 8.0 branch.
Choose the MySQL version
Oracle publishes LTS releases, maintained for a long time, and Innovation releases, which change quickly. On a server, pick an LTS.
| Branch | Type | Status | For whom |
|---|---|---|---|
| MySQL 8.0 | Legacy | End of life since April 2026 | Move to 8.4 or 9.7 |
| MySQL 8.4 | LTS | Maintained | The safest choice, best supported by tools |
| MySQL 9.7 | LTS | Released on April 21, 2026 | New projects that want the latest branch |
This guide uses MySQL 8.4 LTS as the example. Install the latest patch of the branch, listed on the MySQL download page. For 9.7, the steps are the same, with 9.7 instead of 8.4 in the paths.
Prepare the VPS
Windows Server 2025, or 2019 and 2022 for an older server.
2 GB of memory at least, 4 GB for real use.
Administrator access over Remote Desktop.
The Microsoft Visual C++ x64 redistributable, required by MySQL 8.4. Install it from Microsoft's site if it is missing.
Check that port 3306 is free. If the command shows nothing, no program is using it.
netstat -ano | findstr :3306
Install MySQL with the MSI and MySQL Configurator
MySQL Configurator labels are in English.
On the download page, choose version 8.4 LTS and Microsoft Windows, then download the 64-bit MSI Installer.
Run the MSI and choose the Typical setup. The server is installed in
C:\Program Files\MySQL\MySQL Server 8.4.At the end, leave the option to open MySQL Configurator ticked. The server does not start until it is configured.
In Type and Networking, choose the Dedicated type if MySQL is alone on the VPS, or Server if it shares the VPS with IIS or an application. Keep TCP/IP on port
3306. Untick Open Windows Firewall port for network access if no outside machine needs to connect.In Accounts and Roles, give the
rootaccount a long, complex password and store it in a password manager.In Windows Service, keep Configure MySQL server as a Windows service and name the service
MySQL84, the name used in the rest of this guide. Keep Start the MySQL Server at System Startup.Keep the default rights in Server File Permissions, then click Execute in Apply Configuration.
MySQL 8.4 uses caching_sha2_password authentication. The old mysql_native_password is disabled by default. For an old client that needs it, add mysql_native_password=ON to my.ini.
Then check the service and log in as root.
Get-Service MySQL84
cd "C:\Program Files\MySQL\MySQL Server 8.4\bin"
.\mysql.exe -u root -p
At the mysql> prompt, the following command shows the installed version.
SELECT VERSION();
Install MySQL from the command line
To automate the installation, chain the MSI in silent mode and MySQL Configurator in console mode. Replace 8.4.x with the latest patch from the download page, and the root password with yours.
# Exact version to copy from dev.mysql.com/downloads/mysql/
$version = "8.4.x"
$msi = "C:\Temp\mysql-$version-winx64.msi"
New-Item -ItemType Directory -Force -Path "C:\Temp" | Out-Null
Invoke-WebRequest -Uri "https://dev.mysql.com/get/Downloads/MySQL-8.4/mysql-$version-winx64.msi" -OutFile $msi -UseBasicParsing
# Silent installation of the server and MySQL Configurator
Start-Process msiexec.exe -ArgumentList "/i", $msi, "/qn", "/lv", "C:\Temp\mysql_install.log" -Wait
# Configuration: root password, port and Windows service
$bin = "C:\Program Files\MySQL\MySQL Server 8.4\bin"
& "$bin\mysql_configurator.exe" --console --action=configure --password="Ch@ngeMe-Now!" --port=3306 --windows-service-name=MySQL84
# Check
Get-Service MySQL84
& "$bin\mysql.exe" -u root -p -e "SELECT VERSION();"
MySQL Configurator sits in the bin folder, next to mysqld.exe. If it rejects the --console option, its version is too old: run it without options and follow the GUI wizard.
Open remote access
For another server or a tool such as MySQL Workbench to connect, you need an open port, a server listening on the network and an account allowed to connect remotely.
Open port 3306
If you unticked the firewall option in MySQL Configurator, create the rule yourself. Preferably restrict it to the client's address.
# Port 3306 open to everyone
New-NetFirewallRule `
-DisplayName "MySQL 3306" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 3306 `
-Action Allow `
-Profile Any
# Port 3306 restricted to one address (recommended)
New-NetFirewallRule -DisplayName "MySQL 3306 - Allowed IP" -Direction Inbound -Protocol TCP -LocalPort 3306 -RemoteAddress "203.0.113.10" -Action Allow
# Check the rule
Get-NetFirewallRule -DisplayName "MySQL 3306*"
The guide How to open a port in Windows Firewall on a VPS covers these rules. Between two of your VPS, the How to connect two VPS over a private network avoids going through the Internet.
Create a remote account
Never give remote access to root. Create a dedicated account, limited to the client's address and to one database.
-- Account limited to one address
CREATE USER 'appuser'@'203.0.113.10' IDENTIFIED BY 'Ch@ngeMe-Now!';
-- Variant for every address (less safe)
CREATE USER 'appuser'@'%' IDENTIFIED BY 'Ch@ngeMe-Now!';
-- Rights on a single database
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'appuser'@'203.0.113.10';
Also check that MySQL listens on the network. The command SHOW VARIABLES LIKE 'bind_address'; should return * or 0.0.0.0. Otherwise, add this to my.ini and restart the service.
[mysqld]
bind-address = 0.0.0.0
Encrypt remote connections
MySQL 8.4 creates its TLS certificates on first start. The ssl_ca, ssl_cert and ssl_key variables point to their files. Then require TLS for the remote account, and connect the client with --ssl-mode=REQUIRED.
SHOW VARIABLES LIKE '%ssl%';
ALTER USER 'appuser'@'203.0.113.10' REQUIRE SSL;
mysql -u appuser -p -h 203.0.113.50 --ssl-mode=REQUIRED
Tune performance
The configuration lives in C:\ProgramData\MySQL\MySQL Server 8.4\my.ini. Adjust these settings to the VPS memory.
[mysqld]
# InnoDB cache, the most important setting
innodb_buffer_pool_size = 2G
# Maximum simultaneous connections
max_connections = 150
# InnoDB redo log capacity
innodb_redo_log_capacity = 512M
# InnoDB I/O threads (number of cores)
innodb_read_io_threads = 4
innodb_write_io_threads = 4
| VPS memory | innodb_buffer_pool_size | max_connections | innodb_redo_log_capacity |
|---|---|---|---|
| 2 GB | 1G | 75 | 256M |
| 4 GB | 2.5G | 150 | 512M |
| 8 GB | 5G | 300 | 1G |
| 16 GB | 11G | 500 | 2G |
These values assume a VPS used mainly for MySQL. On a VPS that also runs IIS or an application, lower the InnoDB cache. Then restart the service.
Restart-Service MySQL84
Do not add
query_cache_sizeorquery_cache_type, which old tutorials still mention. The query cache was removed in MySQL 8.0, and these lines stop the server from starting.
Secure MySQL
Start with the bundled hardening tool. It offers the password policy, removes anonymous accounts, blocks remote root login and removes the test database. Answer Y to each question.
cd "C:\Program Files\MySQL\MySQL Server 8.4\bin"
.\mysql_secure_installation.exe
The same checks can be done by hand. Remove with DROP USER each anonymous or remote root account that the first query lists.
-- Anonymous accounts and root allowed remotely
SELECT User, Host FROM mysql.user WHERE User = '' OR (User = 'root' AND Host NOT IN ('localhost', '127.0.0.1', '::1'));
-- Examples of removal
DROP USER IF EXISTS ''@'localhost';
DROP USER IF EXISTS 'root'@'%';
-- Test database
DROP DATABASE IF EXISTS test;
Turn on the password policy, a component in MySQL 8.4.
INSTALL COMPONENT 'file://component_validate_password';
SET GLOBAL validate_password.policy = STRONG;
SET GLOBAL validate_password.length = 12;
Give each application only the rights it needs on its own database, never ALL PRIVILEGES ON *.*.
-- Good example
GRANT SELECT, INSERT, UPDATE ON appdb.* TO 'appuser'@'localhost';
-- Avoid
-- GRANT ALL PRIVILEGES ON *.* TO 'appuser'@'%';
To keep a trail, add the logs to my.ini. The general log records every query and grows fast: only turn it on while troubleshooting.
[mysqld]
general_log = 1
general_log_file = "C:/ProgramData/MySQL/MySQL Server 8.4/Data/mysql_general.log"
log_error = "C:/ProgramData/MySQL/MySQL Server 8.4/Data/mysql_error.log"
Require TLS for every remote account, as shown above. Finally, install the branch's patches as soon as they come out and follow the MySQL security advisories.
Back up the databases
To keep the password out of the script, store it in an option file that only administrators can read. Create C:\Scripts\mysql-backup.cnf with this content.
[client]
user=root
password=Ch@ngeMe-Now!
icacls "C:\Scripts\mysql-backup.cnf" /inheritance:r /grant:r "Administrators:F" "SYSTEM:F"
Then save this script as C:\Scripts\mysql_backup.ps1. It backs up each database to a dated file and deletes files older than 7 days.
# MySQL backup with rotation, to be scheduled
$mysqlBin = "C:\Program Files\MySQL\MySQL Server 8.4\bin"
$cnf = "C:\Scripts\mysql-backup.cnf"
$backupDir = "C:\Backups\MySQL"
$retention = 7
New-Item -ItemType Directory -Force -Path $backupDir | Out-Null
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm"
# Every database except system databases
$databases = & "$mysqlBin\mysql.exe" --defaults-extra-file="$cnf" -e "SHOW DATABASES;" --batch --skip-column-names |
Where-Object { $_ -notin @('information_schema','performance_schema','sys','mysql') }
foreach ($db in $databases) {
$dbFile = "$backupDir\${db}_$timestamp.sql"
& "$mysqlBin\mysqldump.exe" --defaults-extra-file="$cnf" `
--single-transaction `
--routines `
--triggers `
--databases $db `
--result-file=$dbFile
if ($LASTEXITCODE -eq 0) { Write-Host "Backup OK: $db" } else { Write-Host "Backup ERROR: $db" }
}
# Rotation
Get-ChildItem -Path $backupDir -Filter "*.sql" |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$retention) } |
Remove-Item -Force
Schedule it every night at 2 a.m., under the SYSTEM account, so it runs without an open session.
$action = New-ScheduledTaskAction `
-Execute "PowerShell.exe" `
-Argument "-NonInteractive -ExecutionPolicy Bypass -File C:\Scripts\mysql_backup.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00"
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 1) `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 5)
Register-ScheduledTask `
-TaskName "MySQL Backup Daily" `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-User "SYSTEM" `
-RunLevel Highest `
-Force
These files stay on the VPS. To keep a copy elsewhere, follow Back up your Windows VPS. For a PHP site under IIS, the article Hosting PHP/MySQL websites on a Windows VPS ties the two together.
Fix common errors
The MySQL service does not start
Read the end of the error log, in the data folder. Common causes are port 3306 already taken, missing rights on the Data folder and an invalid line in my.ini, such as query_cache_size.
Get-ChildItem "C:\ProgramData\MySQL\MySQL Server 8.4\Data\*.err" | Get-Content -Tail 50
Access denied for user 'root', or forgotten password
Start MySQL without privilege checks, just long enough to change the password. --skip-networking blocks every network connection in the meantime.
# Stop the service
Stop-Service MySQL84
# Start MySQL without privilege checks, with its configuration file
& "C:\Program Files\MySQL\MySQL Server 8.4\bin\mysqld.exe" --defaults-file="C:\ProgramData\MySQL\MySQL Server 8.4\my.ini" --skip-grant-tables --skip-networking
# In another PowerShell window, log in without a password
& "C:\Program Files\MySQL\MySQL Server 8.4\bin\mysql.exe" -u root
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY 'Ch@ngeMe-Now!';
EXIT;
# Stop this mysqld, then restart the service normally
Stop-Process -Name mysqld -Force
Start-Service MySQL84
Port 3306 is already in use
# Process holding port 3306
netstat -ano | findstr :3306
# Process name from its PID
Get-Process -Id 1234
Replace 1234 with the PID you found. Stop that program, or change the MySQL port in my.ini (port=3307) and in the firewall rule.
Remote connection fails
The firewall rule exists:
Get-NetFirewallRule -DisplayName "MySQL 3306*".MySQL listens on the network:
SHOW VARIABLES LIKE 'bind_address';returns*or0.0.0.0.The account is allowed from the client's address:
SELECT User, Host FROM mysql.user;.The client's network lets port 3306 out.
"The table is full" error
Most often, the disk is full or a temporary table has hit its limit. Check the free space on C: and the size of each database.
-- Size of each database
SELECT table_schema, ROUND(SUM(data_length+index_length)/1024/1024,1) AS "Size (MB)"
FROM information_schema.tables
GROUP BY table_schema;
-- InnoDB data file and its possible limit
SHOW VARIABLES LIKE 'innodb_data_file_path';
Need help?
Open a ticket from Tickets › Open a Ticket, or from the Contact support tile at the bottom of the VPS page. In the Related Service field, pick the VPS so the team saves time. You can also ask your question on the OuiHeberg Discord.
Windows Server 2025 with the license included, Remote Desktop access and management from the client area.
View plans →