iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Need a server to try this on? Antihost offers Linux VPS plans with NVMe storage, full root access and a 14-day free trial.
On an Ubuntu or Debian VPS, install Node.js with nvm, install MySQL from the distribution packages or the MySQL APT repository, create a separate database account for your application, keep MySQL bound to localhost unless remote access is genuinely required, and allow only the network ports your service needs. This guide assumes SSH access and sudo privileges and takes you from a fresh server to a tested Node.js-to-MySQL connection.
What the finished setup looks like
Your VPS will run two separate services:
- Node.js: your application runtime, normally installed per user with
nvm. - MySQL: a system service managed by
systemd, listening only on the local interface when the application is on the same VPS.
The application connects to a database account created specifically for it. The Linux firewall (UFW) exposes SSH and, if needed, HTTP/HTTPS, but not MySQL to the public internet. This arrangement removes the need to transmit database credentials across the network and substantially reduces the attack surface.
Free tools Windows power users keep installed
One-click scans. No signup required.
Prerequisites and a safe starting point
- An Ubuntu or Debian-family VPS with a supported 64-bit release.
- An account that can run
sudo. - The server’s public IP address and a DNS name if you will publish a web application.
- An SSH client and a second SSH session kept open while changing firewall rules.
- A plan for storing secrets outside source control.
Use a non-root administrative account for routine work. Before enabling UFW or changing SSH-related settings, verify that a second terminal can log in. If a firewall change is wrong, that second session may be the only way to undo it without console access from your VPS provider.
1. Update the VPS and confirm the operating environment
Connect over SSH and identify the distribution:
ssh your-admin-user@your_server_ip
cat /etc/os-release
uname -m
Update package metadata and installed packages:
sudo apt update
sudo apt upgrade -y
Reboot only when the package manager or your VPS provider indicates that a restart is required. Reconnect after a reboot and confirm that sudo still works before continuing.
#1 Best Overall
2. Install Node.js
Why nvm is usually the best fit
The official Node.js download guidance provides an nvm path for Linux. nvm installs Node.js in a user’s home directory, lets that user switch release lines, and avoids replacing distribution files. It is a strong choice for a single application account or for developers who must test more than one Node version.
Open the official Node.js download page and run the current Linux nvm installation command shown there. The installer changes as nvm releases change, so copying the command from that page is safer than retaining an old command in a deployment document. After installation, load nvm in the current shell:
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutesource ~/.nvm/nvm.sh
Install the desired release line. For a long-lived production service, use the current LTS line; for evaluating new platform features, use Current only after checking compatibility with your dependencies:
nvm install --lts
nvm alias default lts/*
node -v
npm -v
If you need a named major line, use its major number instead, for example nvm install 24. The Node.js release page is authoritative for channel labels and patch versions. A release-page snapshot used for this guide listed Node.js v24.21.0 as LTS and v26.8.2 as Current; those labels and version numbers change, so verify them before installation.
Make nvm available after login
Normally the installer adds initialization to your shell profile. Start a new SSH session and run:
command -v nvm
node -v
npm -v
If the command is missing, load the nvm script manually with source ~/.nvm/nvm.sh, then inspect your shell profile for the nvm initialization lines. A service launched by a different Linux user will not automatically see the Node.js installation in your account; install and select Node for the account that will run the service, or deliberately configure a system-wide runtime.
Recommended Free Tools
Rank #2
Node.js installation choices
| Method | Version control | Reproducibility and updates | Best use |
|---|---|---|---|
nvm |
Install and switch major or minor release lines per user. | Reproducible when the project records its Node line; updates are controlled by that user. | Application users, development, and servers hosting several Node applications. |
| Distribution packages | Version follows the Ubuntu or Debian repository and may lag upstream. | Integrated with apt security updates; exact versions depend on the distribution release. |
Simple system administration when the repository version satisfies the application. |
| System-wide binary installation | One runtime can serve all users, but switching versions requires administrative changes. | Centralized, yet upgrades must be planned so every application remains compatible. | Controlled service accounts and images managed as a whole. |
Do not mix an apt-installed Node.js executable with an nvm installation without checking command -v node. The first executable in the shell’s PATH is the one that will run.
3. Install MySQL
Install the Ubuntu or Debian package
On Ubuntu, the documented package command is:
sudo apt update
sudo apt install mysql-server
On systemd-based systems, check that the service started automatically:
sudo systemctl status mysql
# The service command is also available on Ubuntu:
sudo service mysql status
A running service normally shows active (running). Exit the status screen with q. If the package manager asks configuration questions, keep the terminal open until installation completes, then check the status again.
Choose an installation source deliberately
Native distribution packages are the simplest to maintain and are integrated with the VPS’s security-update process, but they can lag upstream MySQL releases. MySQL also documents its APT and Yum repositories, downloadable packages and binaries, source builds, and containers as separate installation methods.
| Source | Release freshness | Support and upgrade considerations | When to choose it |
|---|---|---|---|
| Ubuntu/Debian native repository | May be older than upstream. | Best integration with the distribution and its package tools; upgrades follow the OS release policy. | Most VPS deployments where the packaged version meets application requirements. |
| MySQL APT repository | Usually offers newer MySQL series than a stable distribution release. | Follow MySQL’s repository choices and upgrade guidance; test major upgrades before production. | When a specific upstream series or feature is required. |
| Oracle packages or binaries | Can provide a release independently of the OS repository. | You assume more responsibility for compatibility, patching, and service integration. | Controlled environments with a documented vendor-package policy. |
| Container | Determined by the image tag. | Requires a container runtime, persistent-volume design, backup plan, and container lifecycle management. | Platforms already standardized on containers. |
Use one source consistently. Mixing a native package with files from another distribution or repository can create dependency and upgrade conflicts.
4. Create a database and a least-privilege account
Do not configure the application to use MySQL’s administrative root account. Ubuntu documentation notes that the default root account is local and may authenticate through auth_socket; that is useful for local administration but is not an application credential.
Open a local administrative MySQL session:
sudo mysql
Run SQL that creates a database and an account restricted to that database. Replace the sample secret with a long, unique value generated for this server:
CREATE DATABASE appdb
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost'
IDENTIFIED BY 'use-a-long-random-secret-here';
GRANT SELECT, INSERT, UPDATE, DELETE
ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Grant additional privileges only when the application genuinely needs them. For example, schema migrations may require temporary ALTER, CREATE, or INDEX privileges; do not leave broad administrative rights on a long-running web account. Keep the account host as localhost when the application and database share this VPS.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteStore the password in the service environment or a root-readable secret file, never in a Git repository, JavaScript source file, shell history, or a client-side bundle.
5. Keep MySQL private by default
Confirm the bind address
MySQL configuration files live below /etc/mysql. Find the active bind-address setting:
sudo grep -R "^[[:space:]]*bind-address" /etc/mysql
For a Node.js application on the same VPS, set the server to listen on 127.0.0.1 (or leave the distribution’s secure local setting in place). Edit the file reported by the search, then restart MySQL:
sudo systemctl restart mysql.service
sudo systemctl status mysql
Using localhost in the application usually selects MySQL’s local socket; using 127.0.0.1 forces a local TCP connection. Either is private when MySQL is bound only to the loopback interface, but use the same host value when creating and connecting the account.
Rank #4
When remote database access is unavoidable
Changing bind-address to a private network address or another interface allows network clients to reach MySQL. Treat that as an exception:
- Bind only to the required private interface, not every interface.
- Create an account whose host pattern is restricted to the actual client address or private subnet.
- Allow TCP port 3306 in UFW only from those source addresses.
- Use an encrypted transport such as a private tunnel or TLS; never expose an unrestricted, password-only MySQL listener to the public internet.
- Recheck the setting after upgrades, because package configuration files can change.
6. Configure UFW without locking yourself out
Ubuntu identifies UFW as its default firewall configuration tool. Before enabling it, allow the SSH port you actually use. The following example assumes the standard SSH port and a web application using HTTP and HTTPS:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status numbered
sudo ufw enable
sudo ufw status verbose
If SSH runs on a nonstandard port, allow that port explicitly instead of OpenSSH. Keep your second SSH session open while testing a new ruleset. Do not add a public 3306/tcp rule for a local Node.js application. If a remote design requires it, replace a broad rule with a source-restricted rule, for example:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
sudo ufw allow from 203.0.113.10 to any port 3306 proto tcp
Use the real fixed client address, not the documentation address shown in that example, and remove the rule when the remote requirement ends.
7. Connect a Node.js application to MySQL
Install a MySQL driver in the application
Create a project under the account that will run it:
mkdir -p ~/myapp
cd ~/myapp
npm init -y
npm install mysql2
Create db-test.js:
const mysql = require('mysql2/promise');
async function main() {
const pool = mysql.createPool({
host: process.env.DB_HOST || '127.0.0.1',
port: Number(process.env.DB_PORT || 3306),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 5
});
try {
const [rows] = await pool.query('SELECT 1 AS ok');
console.log(rows);
} finally {
await pool.end();
}
}
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
Set credentials only in the shell that runs the test:
Best Value
export DB_HOST=127.0.0.1
export DB_PORT=3306
export DB_USER=appuser
export DB_PASSWORD='use-the-same-secret-as-the-mysql-account'
export DB_NAME=appdb
node db-test.js
A successful run prints an object containing ok: 1. For a persistent service, provide these variables through your service manager’s protected environment mechanism and restrict any environment file to the service account. Never print the password while diagnosing a connection.
Check permissions, not just connectivity
A connection test proves that the credentials work, not that the account is correctly limited. Run an intended write or read against a test table, then deliberately attempt an operation the application should not have, such as creating another database. The denied operation confirms that least privilege is active. Remove test data before putting the application into production.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.8. Manage startup, shutdown, and logs
Debian and Ubuntu package installations use systemd for MySQL startup and shutdown. The core commands are:
sudo systemctl status mysql
sudo systemctl restart mysql
sudo systemctl stop mysql
sudo journalctl -u mysql --no-pager -n 100
Use restart after a configuration change, and inspect the journal immediately if the service does not return to active (running). For Node.js, ensure the process is supervised by the service mechanism you have selected and runs as a dedicated, non-root user. Whatever supervisor you use, confirm that it loads the same Node.js version and environment variables you tested interactively; login-shell nvm settings are not automatically inherited by every service.
9. Troubleshoot the common failure modes
| Symptom | Likely cause | Checks and fix |
|---|---|---|
nvm: command not found |
The shell has not loaded nvm. | Run source ~/.nvm/nvm.sh, open a new session, and verify the profile initialization. Confirm that the service account has its own Node installation. |
| Unexpected Node or npm version | Another executable appears earlier in PATH. |
Run command -v node, select the intended line with nvm use, and set nvm alias default. Avoid mixing package-manager and nvm installations unintentionally. |
| MySQL is inactive or failed | Configuration, permissions, disk, or an upgrade problem. | Run sudo systemctl status mysql and sudo journalctl -u mysql. Correct the first configuration error shown, then restart and check status again. |
Access denied for user |
Wrong password, account host, or grant. | Enter with sudo mysql, inspect the account and grants, and ensure the application uses the same host form (localhost versus 127.0.0.1) used by the account. |
| Connection refused or timeout | MySQL is stopped, bound to another address, or blocked by a firewall. | Check service status, inspect bind-address, test locally first, and review UFW rules. A local application should not need an inbound 3306 rule. |
| Remote client cannot connect | MySQL listens only on loopback, or a source-restricted rule is missing. | Verify the private bind address, account host restriction, encryption design, and UFW source rule. Do not solve the problem by opening 3306 to everyone. |
10. Final verification checklist
- Start a fresh SSH session and confirm
node -vandnpm -vfor the application account. - Confirm
sudo systemctl status mysqlreports a running service. - Verify that the application database and dedicated user exist.
- Run the Node.js connection test with credentials supplied through the environment.
- Confirm the application can perform its intended queries and that unnecessary operations are denied.
- Run
sudo ufw status verbose; SSH and required web ports should be present, while public MySQL access should be absent unless explicitly designed and source-restricted. - Review
sudo journalctl -u mysqlafter any restart and record the Node.js and MySQL versions in your deployment notes.
At this point Node.js is installed with a deliberate release policy, MySQL is managed as a persistent system service, the application has its own limited credentials, and the database is not publicly reachable by default.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

