Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
source ~/.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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Store 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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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:

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.Support on Ko-Fi

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

  1. Start a fresh SSH session and confirm node -v and npm -v for the application account.
  2. Confirm sudo systemctl status mysql reports a running service.
  3. Verify that the application database and dedicated user exist.
  4. Run the Node.js connection test with credentials supplied through the environment.
  5. Confirm the application can perform its intended queries and that unnecessary operations are denied.
  6. 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.
  7. Review sudo journalctl -u mysql after 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.