If aaPanel reports that Python virtual-environment creation returned non-zero exit status 1, the popup alone does not identify the cause. In our Ubuntu lab, the selected system Python lacked ensurepip support because its venv package was not installed. Installing the matching Ubuntu package fixed the creation failure.
The actual aaPanel virtual-environment creation failure in the lab.
Tested: Ubuntu 22.04.5 LTS, aaPanel Free 8.0.7, selected interpreter /usr/bin/python3.10. Tested on 14 October 2026. The package and paths below apply to this specific combination; do not assume every exit-status-1 error has the same cause.
1. Record the failed command
The popup showed that aaPanel attempted:
Record the interpreter and target directory. Do not delete a working environment to investigate an unrelated creation error.
2. Read the underlying error
Over SSH, repeat the creation command only for the newly failed lab target, or choose a fresh unused diagnostic directory. In our test, the error explained that ensurepip was not available and recommended python3.10-venv.
Use the exact selected interpreter. Running a different python command could test another Python installation and hide the problem.
3. Check the matching package
On our Ubuntu server:
Code:
apt-cache policy python3.10-venv
It showed Installed: (none), with a candidate available from Ubuntu's repository. Confirm the selected Python version and repository source before installing a package. Other distributions and vendor-managed interpreters have different prerequisite workflows.
4. Install venv support
As an administrator:
Code:
sudo apt-get install python3.10-venv
Our package manager installed python3.10-venv and its pip/setuptools wheel prerequisites. No unrelated packages were removed. Keep package-manager errors if installation fails; do not continue as if the prerequisite is ready.
5. Retry the aaPanel form
Return to Website → Python Project → Python Environment. Dismiss the error popup, then retry the new environment's creation form. We used the same selected system interpreter and environment name. It succeeded and appeared in the environment list.
A failed attempt may leave a partial directory. In our lab the retry completed it successfully. Do not recursively delete a directory unless you have verified that it belongs only to the failed disposable attempt and no project uses it.
The lab returned Python 3.10.12, pip 22.0.2 inside the new environment and True for the isolation check. Substitute the actual interpreter path from your environment.
If the error continues
Check available disk space, parent-directory permissions, the selected interpreter and the complete command error. Missing ensurepip was our verified cause, not a universal diagnosis. Avoid changing aaPanel's own environment or reinstalling the whole panel before identifying the failure.
Create a separate virtual environment for a Python application so its packages do not get mixed into aaPanel's own environment. This walkthrough uses aaPanel's Python Environment controls and verifies the resulting interpreter and pip over SSH.
Tested lab: Ubuntu 22.04.5 LTS, aaPanel Free 8.0.7, system Python 3.10.12, environment af_python_lab. Tested on 14 October 2026. This records the system interpreter already present in our lab; for a new production application choose a currently maintained Python release compatible with the app and your operating-system support policy.
1. Open Python Environment
Go to Website → Python Project → Python Environment. The environment list can include the operating system's interpreter and aaPanel's own environment. Identify which is which before installing packages or changing anything.
We used the system interpreter as the source and created a new environment. We did not install application packages into aaPanel's own pyenv.
2. Create the environment
Click Create virtual environment. Enter a unique name such as af_python_lab, select the intended source interpreter and add a useful remark. In this version, clicking the source's table row selects its radio control.
Create virtual environment: name, source interpreter and tutorial remark.
Click Confirm and wait for completion. Do not assume an environment is ready merely because a directory was created.
3. Handle missing venv support if necessary
Our first attempt failed. A direct check showed that the system interpreter lacked ensurepip support because Ubuntu's python3.10-venv package was absent. We installed the package from Ubuntu's repository and retried the same creation form. It then succeeded.
For this specific Ubuntu/Python combination, the prerequisite command was:
Code:
sudo apt-get install python3.10-venv
Other interpreter versions require their corresponding package or installation method. Do not use this package name blindly on another distribution. The separate troubleshooting guide covers diagnosis of this error in more detail.
4. Confirm the new list entry
The list should show your new environment as virtual env, with the expected Python version and remark. In our test af_python_lab appeared alongside the original system and panel environments.
The new environment is listed separately from the panel and system environments.
It was not assigned to a project yet, so Using project showed a dash. Creating an environment alone does not deploy a website, map a domain or enable HTTPS.
5. Verify Python and pip
Find the environment's actual interpreter path. Our panel used the directory below, including its spelling:
Our outputs identified Python 3.10.12 and pip 22.0.2 inside this environment. Using python -m pip ties the package installer to that interpreter rather than whichever pip command happens to be on PATH.
The lab returned the environment directory, /usr as the base prefix and True. The differing prefixes confirm this interpreter is running in a virtual environment.
You can invoke the interpreter by its absolute path without activating an interactive shell. Activation is a convenience for that shell, not proof that a separately started aaPanel project uses the same interpreter.
7. Prepare for application deployment
Keep the app's source directory separate from the interpreter environment. Install its required packages into the intended application environment, then choose that environment when adding the Python project. Check the run user, start command and listening port before mapping a domain.
Do not remove an environment while a project uses it. When migrating, retain the source and dependency specification and recreate the environment deliberately instead of assuming that moving the environment folder is sufficient.
Result: aaPanel created the isolated environment after the missing Ubuntu prerequisite was installed. The environment's Python, pip and prefix-isolation checks passed. No Python website has been published by these steps alone.
A .env file holds application configuration, but merely creating the file does not guarantee Node.js will read it. This guide explicitly loads a .env file with Node.js 24 and verifies the result using a small localhost-only app on an aaPanel server.
Environment-file workflow based on the isolated lab test.
Tested: Ubuntu 22.04.5 LTS, aaPanel Free 8.0.7 and Node.js 24.21.0 on 14 October 2026. The sample ran as www in /www/af-node-env-lab and listened on 127.0.0.1:3104. We tested the SSH startup command below; we did not change the live aaPanel project's start command.
1. Separate configuration from application code
Configuration can include a port, a display label or connection settings. This example uses only non-secret values. Do not place real credentials into tutorial screenshots, public repositories or responses returned to visitors.
Create a new practice directory as an administrator:
Choose a directory that does not already contain an application. This sample is outside the live website's document root.
2. Create the environment file
Create /www/af-node-env-lab/.env with:
Code:
APP_LABEL=AdminsForum environment lab
PORT=3104
A leading dot can make the file less obvious in file listings; it does not make it encrypted or safe to expose over HTTP. If using a file manager, enable its hidden-file view when needed.
Mode 600 allows the owner to read and write. It is not isolation between applications that all run as www. Use suitable account isolation and secret management for real deployments.
3. Create a sample application
Create /www/af-node-env-lab/server.js with:
Set its owner to www and allow that user to read it. The app reads configuration from process.env. It exposes only the harmless sample label, not the whole environment. This demonstration validates presence of the two sample values; a real application should validate its complete configuration, including allowed port ranges.
4. Load the file explicitly
Use the installed Node.js binary and an absolute path:
Keep this foreground process running while testing from a second SSH session. Replace the runtime path with your installed version. This command uses Node's built-in environment-file support; no dotenv package is required for this tested Node.js 24 example.
The --env-file option must be passed to Node before the application filename. Using an absolute file path avoids confusion about the working directory. Check your runtime's documentation before using this option with an older release.
5. Verify the result without displaying secrets
From another SSH session:
Code:
curl -i http://127.0.0.1:3104/
Expect HTTP 200 and:
Code:
AdminsForum environment lab
Our test returned exactly that label. We also checked the file's permissions were 600 and confirmed that the existing HTTPS website still returned 200. Stop the foreground test with Ctrl+C when finished. Our temporary process was stopped after verification.
Do not verify real credentials by printing process.env or returning secret values from a web endpoint. For a required secret, check its presence or test the intended service connection without exposing its value.
6. Connect this to your aaPanel deployment
For a managed application, the configured start command must load the intended environment file every time the process starts. An export in your interactive SSH shell is not proof that a separately started panel process receives the same environment.
Review the app's package.json start script or custom startup command and its expected working directory. Preserve the selected runtime, run user and port. Test in a separate project before changing a live start command, then verify application restart and reboot recovery. This guide proves the environment-loading command; panel-managed persistence for this practice app was not configured here.
7. Troubleshoot missing values
Missing configuration: Check that the startup command includes --env-file and that variable names match the code.
File not found: Check the absolute path and hidden filename.
Permission denied: Check the file owner, mode and parent-directory access for the run user.
Unsupported option: Confirm which Node binary is running and whether it supports --env-file.
Port conflict: Check for an existing listener before launching another copy.
After changing a .env file, restart the relevant process and verify its behaviour; a running app should not be assumed to reload the file automatically.
Installing a Node.js runtime is only the first step. An application may also need packages from npm. This guide installs Express in a separate lab folder on an aaPanel server, fixes a writable-cache problem and verifies a clean lockfile-based installation.
npm dependency workflow based on the isolated lab test.
Tested: Ubuntu 22.04.5 LTS, aaPanel Free 8.0.7, Node.js 24.21.0, npm 11.19.0 and Express 5.3.0 on 14 October 2026. Commands ran as www in /www/af-npm-lab. This is an SSH workflow, not a demonstration of aaPanel's Module button. The live website was not modified.
1. Confirm the intended Node.js runtime
Install Node.js first using the aaPanel runtime guide. Check the Node version supported by your application and dependencies. Use the same intended runtime for package installation and app execution.
Our runtime is under /www/server/nodejs/v24.21.0/bin. Replace that path if your installed version differs. A shell's node command can point to a different runtime from the one selected for an aaPanel project.
2. Create a separate practice directory
As an administrator over SSH, choose a directory that does not already contain an application:
The final command opens a shell as www. The remaining npm commands below belong in that shell. Do not initialise a new package.json over a real application's existing manifest. For an existing project, use its supplied manifest, lockfile and installation instructions.
3. Set the runtime and a writable npm cache
Code:
export PATH=/www/server/nodejs/v24.21.0/bin:$PATH
export npm_config_cache=/www/af-npm-lab/.npm-cache
cd /www/af-npm-lab
node --version
npm --version
We verified v24.21.0 and 11.19.0. These exports affect the current shell; they are not a permanent change to aaPanel's project configuration.
Our first attempt encountered a shared npm cache at /www/server/nodejs/cache that www could not write to. A separate writable cache fixed the problem without running npm as root or making shared directories writable to everyone. For real deployments, choose a protected cache location outside publicly served files and ensure the intended run user owns it.
4. Initialise only the new sample project
Code:
npm init -y
This creates package.json for the practice folder. Review the result. Its default test script is a placeholder, not a passing application test.
5. Install the example dependency
Code:
npm install express --ignore-scripts --registry=https://registry.npmjs.org
npm ls express
The test used the official npm registry. Express installed as version 5.3.0, and npm ls showed it as a dependency of af-npm-lab. The command also produced package-lock.json and populated node_modules.
We used --ignore-scripts for this controlled example. Some real packages require installation scripts or build steps; disabling those can leave them unusable. Follow the application's documented requirements rather than applying this flag indiscriminately.
6. Understand the files
package.json: Declares the project and its dependency requirements.
package-lock.json: Records npm's resolved dependency tree for this project.
node_modules: Contains installed packages; its presence alone does not prove a working app.
.npm-cache: Our chosen local download cache, not application source.
Preserve the manifest and lockfile in your source and recovery process. Do not mix package managers casually: a project built around a Yarn or pnpm lockfile needs its corresponding workflow.
7. Verify a clean installation from the lockfile
Only run the following in the isolated practice folder:
Code:
sha256sum package-lock.json
npm ci --ignore-scripts --registry=https://registry.npmjs.org
sha256sum package-lock.json
npm ls express
The two lockfile checksums matched in our test, and Express remained installed. npm ci requires an existing matching lockfile, installs the whole project and replaces an existing node_modules directory. It is therefore not a harmless diagnostic command to run inside a serving production release.
If the manifest and lockfile disagree, npm ci reports an error rather than silently updating the lockfile. Resolve that mismatch in a controlled development or build workflow; do not delete the lockfile merely to hide the problem.
8. Check that Node can load the package
From the practice directory:
Our output was EXPRESS_LOAD_PASS. This confirms that Node can import Express and create an application object. It does not start a web service or prove an entire application, database or domain works.
npm's audit output reported zero known vulnerabilities during this test. That result is time-dependent and is not a guarantee that a package or application is secure. The lab also displayed a warning about an unknown global --init.module configuration; it did not prevent these checks from passing.
9. Common installation errors
EACCES or cache write failure: Check directory ownership and use a writable cache for the intended user. Avoid permissions 777 or a root install as shortcuts.
npm ci needs a lockfile: Check you are in the right directory and have the project's npm lockfile.
Module not found: Check the working directory, installed dependency and runtime used to start the app.
Unsupported engine: Compare package requirements with the selected Node version.
Registry or certificate error: Check connectivity and registry settings; do not bypass TLS validation to force a download.
10. Return to aaPanel deployment
Type exit to leave the www practice shell. For a real application, prepare dependencies and any required build in a separate release directory, then configure the correct runtime and start command in aaPanel. Restart the intended project and verify its expected response. See Deploy a Node.js app with a domain and HTTPS.
Lab result: Express loaded successfully; npm ci passed; the lockfile checksum stayed unchanged. The isolated practice directory is retained. No public application or production dependency update was performed.
A backup is useful only if you can restore and run the application. This guide backs up our aaPanel-managed Node.js sample, extracts it into a separate folder, compares the restored files and runs the copy on a temporary localhost port. The live website is not replaced.
Backup and isolated restore workflow, based on the lab test below.
Tested setup: Ubuntu 22.04.5 LTS, aaPanel Free 8.0.7, Node.js 24.21.0. Source folder /www/wwwroot/af-node-lab; project afnodelab; live domain node.adminslab.com. Tested on 14 October 2026. These steps use SSH on the aaPanel server; they do not demonstrate aaPanel's native Backup / Restore button.
1. Decide what the backup must include
For this dependency-free sample, we preserved server.js, package.json and yarn.lock. For a real application, also identify configuration, required environment variables, uploaded files, generated assets and external services.
A source-file archive is not a database backup or a full aaPanel configuration backup. Record the project name, runtime, run user, startup command, port, domain mapping and Boot setting separately. Store secrets securely. Do not put an archive containing credentials under a publicly served website directory.
2. Check the live app before starting
Code:
curl -I https://node.adminslab.com/
ls -l /www/wwwroot/af-node-lab
df -h /www
Use your own domain and directory. Confirm the app is healthy and enough disk space is available. Our live app returned 200 before the test. For an application writing files or a database, plan a consistent backup process; copying changing files does not automatically produce a consistent recovery point.
3. Create the archive outside the website root
The following commands are for our lab. Use a unique archive filename to avoid overwriting an older backup:
The -C option changes the base directory before archiving. The archive contains the af-node-lab folder rather than absolute server paths. We excluded node_modules for this sample; a real app needs a reproducible dependency installation or another deliberate dependency-backup strategy. Preserve its lockfile and build requirements.
Our archive was 878 bytes because the sample contains only a few tiny source and lock files. That size is not representative of a real application.
4. Inspect the archive and keep an off-server copy
Code:
sudo tar -tzf /www/backup/af-node-20261014.tar.gz
sudo sha256sum /www/backup/af-node-20261014.tar.gz
Check that the expected files appear. A readable archive list is useful, but it does not prove the app can run after restoration. Transfer a protected copy to a separate backup location and record its checksum. Our lab test verified a local archive and restore; an off-server transfer was not part of this test.
5. Restore into a new directory
Choose a directory that does not already exist. Never extract over the live app as your first recovery test:
Code:
sudo mkdir /www/af-node-restore-20261014
sudo tar -xzf /www/backup/af-node-20261014.tar.gz -C /www/af-node-restore-20261014
The restored app is now under /www/af-node-restore-20261014/af-node-lab. This directory is separate from the live application's document root.
Repeat for package.json and yarn.lock. Matching digests confirm identical file contents at this point. Our test compared all three and they matched. A file checksum does not validate database contents or application behaviour.
6. Prepare the restored copy for an isolated test
Set ownership only on the separate test directory:
In the restored server.js only, change the sample port from 3100 to 3102 and update its startup message to match. Leave the live server.js untouched. Confirm that the temporary port is unused:
Code:
ss -ltn | grep ':3102'
No matching listener should be present before this test. Do not use an occupied port. Keep the sample bound to 127.0.0.1 so the temporary copy is not exposed publicly.
Replace the runtime path with your installed version. Real applications may also need dependencies, compilation and test environment configuration. Do not connect a recovery test to production services that could send mail, charge payments or modify live data.
7. Run the copy and check its response
In one SSH session, start it in the foreground as www:
The restored sample should return HTTP 200 and the AdminsForum Node.js Lab page. The live domain should still return 200 independently. We verified the restored response and the live domain during our test.
When finished, stop the foreground test with Ctrl+C. Our test also stopped its temporary process after validation; the live app remained running. Confirm that the temporary listener has gone before reusing the port.
8. Record the recovery result
Our test result was:
Code:
server.js file hash: MATCH
package.json file hash: MATCH
yarn.lock file hash: MATCH
Restored app on localhost:3102: EXPECTED PAGE
Live HTTPS website: 200
The archive and isolated restored folder were retained for later inspection. This proves recovery of this small source-based sample, not a full server or database restore.
9. Before replacing a real deployment
Keep the current release and its backup, restore any database consistently, verify dependencies and secrets, then plan the switch and rollback. Use aaPanel to configure the intended project directory and runtime only after the recovery copy passes its checks. Repeat response tests after the change. This guide deliberately stops before a live cutover.
For a Node.js website behind aaPanel's Nginx mapping, a 502 Bad Gateway often means the web server could not get a usable response from the application. Start by testing the app locally, then compare its listening port with the mapping. Reinstalling Node.js or disabling the firewall is not a useful first diagnostic step.
Tested lab: aaPanel Free 8.0.7, Node.js 24.21.0, project afnodelab, app at 127.0.0.1:3100, domain node.adminslab.com. On 14 October 2026 we stopped only this sample app using aaPanel's native project controls, observed HTTPS 502, started it again and observed HTTPS 200. The app is restored and running.
1. Identify which layer fails
Run these checks over SSH on the VPS:
Replace the sample port and domain with yours. The local request reaches the Node application directly. The domain request goes through DNS, TLS and the web server. Compare the results:
Local connection refused + public 502: Check whether the app is running and whether the port is correct.
Local 200 + public 502: Check the mapping's upstream address and port, then the website error log.
Both return 200: The fault may have cleared. Check logs and monitoring before changing settings.
TLS validation fails: Investigate the certificate separately; a certificate error is different from an HTTP 502 response.
2. Find the actual listening port
For our sample:
Code:
ss -ltnp | grep ':3100'
The expected listener is 127.0.0.1:3100. We also tested 127.0.0.1:3101, where no app was listening, and curl returned connection refused. This demonstrates why guessing an upstream port is unreliable. We did not deliberately change the live mapping to that incorrect port.
If your app listens on another port, use that port in the local check and the aaPanel project settings. For an app behind a local reverse proxy, a localhost-only listener is appropriate. It does not need its application port opened publicly just to be reached by Nginx on the same machine.
3. Check project configuration
Open Website → Node.js Project, click the project name and inspect Project config. Compare the directory, start command, selected runtime, run user and port against the application's own configuration.
Reference screenshot from the sample deployment: project port 3100, www user and Node.js runtime.
Changing the Project Port field does not rewrite a hard-coded port in server.js. The two must agree. Also check that the selected directory contains the expected package.json and startup file. Our sample uses start → node server.js and runs as www.
4. Read Project log before restarting
Open Project log. Look for the latest startup message and any error that precedes it.
Reference startup log from the sample deployment shows the app listening on localhost:3100.
Module not found: Check the application's dependency installation and selected runtime.
Permission denied: Check the run user's access to the app directory and required resources.
Address already in use: Identify the existing listener; do not start duplicate copies.
App starts then exits: Read the exit error and check required environment variables or external services.
Do not switch the app to root or use permissions 777 to hide an access problem. Fix the specific missing permission instead.
5. Check the mapping
In Domain Manager, confirm the intended hostname is present. In Mapping, confirm mapping is enabled. Check the relevant web-server configuration read-only before making changes.
That upstream matches the app's listener. Configuration filenames differ between projects and versions; inspect the relevant site's configuration rather than editing an unrelated file. Prefer the panel's controls for changes, and validate the web-server configuration before reloading it.
6. Restore a stopped application
Use Service status → Start for a stopped project, or Restart when appropriate for the selected app. Confirm the action and wait for startup to finish. Repeat the local and domain checks; a Running label alone does not prove the application returns the expected page.
Our controlled test produced:
Code:
Native project stop: Stopped successfully
HTTPS while stopped: 502
Native project start: Successfully started
HTTPS after recovery: 200
This test changed only the isolated sample project's process state and restored it in the same test. It did not require changing DNS, TLS, firewall rules or the working mapping.
7. Watch for a reboot startup delay
We previously saw a brief 502 while the VPS was coming back after reboot. The app then recovered automatically with Boot enabled. See Auto-start Node.js apps after reboot in aaPanel for the recovery test.
Allow a reasonable startup period and check the app log. If 502 persists, investigate it; do not assume that every 502 will resolve itself. If you manually start the app, record the intervention so you do not mistake it for successful automatic recovery.
8. Confirm the result
Check the expected response body as well as the status:
Use normal TLS validation. Save the relevant error and the change that fixed it. If requesting support, include the aaPanel version, Node runtime, start command, listening port and redacted error excerpt. Keep credentials and private configuration values out of the post.
A Node.js app that runs today may still fail after a VPS restart if automatic startup is disabled. This guide shows where aaPanel's Boot option is, how to read the right logs, and how to prove that an app recovers after a real reboot.
Lab result: We enabled Boot for our sample project, rebooted the tutorial VPS and verified that it started automatically without pressing Start. There was a temporary 502 while the server was coming back; a later HTTPS check returned 200 with the expected page. The app continued running as www on 127.0.0.1:3100.
Tested: Ubuntu 22.04.5 LTS, aaPanel Free 8.0.7 and Node.js 24.21.0 on 14 October 2026. Project: afnodelab. Domain: node.adminslab.com. This is a disposable tutorial server; schedule a maintenance window before rebooting a server that hosts real users.
Replace the port and domain for your own app. Both checks should return 200 for this sample. The local request checks the application directly; the HTTPS request checks the domain, web-server mapping and certificate as well.
2. Find the application log
Open Website → Node.js Project and click the project name. Select Project log. Read the latest startup output, including errors before the final line.
Our sample logs this line when it starts:
Code:
AdminsForum lab listening on http://127.0.0.1:3100
An error such as module not found, permission denied or address already in use points to different problems. Check the start script and files for missing modules, the selected user's permissions for access errors, and the port for address conflicts. Do not repeatedly restart without reading the error.
Project log and Website log are not interchangeable. The project log concerns the application process. Website logs concern requests handled by the mapped web server. A 502 can occur when Nginx is running but the application behind it is not ready.
3. Restart only the application when appropriate
Open Service status → Restart and confirm the project restart. This affects the selected app rather than rebooting the whole VPS. Repeat the local and HTTPS response checks afterwards.
We tested a project restart in the deployment guide and confirmed the expected page returned. A successful project restart alone does not prove startup after a full machine reboot: those are separate tests.
4. Enable startup after reboot
Open Project config. Review the existing directory, start script, port, www user and Node runtime. Check Boot → Follow the system to start the service, then click Save. Wait for Modify the project successfully.
The Boot checkbox is enabled for afnodelab and saved successfully.
Do not assume that checking the box without saving has changed the server configuration. Keep the application files in a persistent directory and make sure its runtime remains installed. Startup cannot repair a broken start command or missing dependency.
5. Record the current boot before testing
Over SSH, record:
Code:
cat /proc/sys/kernel/random/boot_id
uptime -s
The boot ID lets you distinguish an actual server reboot from merely reconnecting your terminal. Also check the app's current listener:
These checks match our sample. Other applications may use a different start command. Our pre-reboot listener and process were present, and the app ran as www.
6. Perform a controlled VPS reboot
Make sure you have provider-console access, current backups and no installation or migration in progress. Notify users if this is a live service. A full reboot interrupts all services on that VPS, including websites and mail.
On the authorised lab server we ran:
Code:
sudo systemctl reboot
Expect SSH to disconnect. Give the machine time to start, then reconnect. Do not open a second copy of the app manually during this test; doing so would obscure whether automatic startup worked and could introduce a port conflict.
7. Verify automatic recovery
After reconnecting, repeat:
A changed boot ID confirms a new boot. The listener, process owner and responses confirm that the app returned. New process IDs are normal after a reboot.
In our test, the first HTTPS check shortly after startup returned 502. A later check returned 200 automatically, and we verified the expected response body, a localhost-only listener and a www-owned Node process. We did not manually start the project between those checks. Do not treat a short startup delay as proof that the Boot setting failed; also do not assume that every persistent 502 is harmless.
The expected sample page is available over HTTPS after the VPS reboot.
8. If recovery does not complete
Check whether the VPS, aaPanel and web server are available. Sign back into aaPanel if the reboot ended your panel session. Review the project status, Boot setting and latest Project log. Test localhost before investigating DNS or SSL.
No listener: The app has not started, is starting slowly or exited. Read the application log and check its runtime and start command.
Local response works, domain returns 502: Check the web-server mapping and its configured upstream port.
Permission errors: Check the run user's access to files and required resources; avoid switching to root as a shortcut.
Port conflict: Identify the process using the port. Do not launch another duplicate process to fix it.
Only HTTPS fails: Check the certificate and web-server configuration separately from the app.
If you need a manual Start to restore service, record that intervention and re-test automatic startup later. A manually recovered app has not passed an automatic-start test.
What this test proves
The Boot option worked for this sample on the tested aaPanel version. It does not prove that a different application, database dependency or background queue will recover in the same way. Repeat recovery testing for each real deployment, including its dependencies and expected response.
This walkthrough creates a small Node.js website, starts it as the www user in aaPanel, connects a domain through Nginx and enables HTTPS. It includes the actual sample files and checks used in our lab, so you can confirm each stage before moving on.
Tested setup: Ubuntu 22.04.5 LTS, aaPanel Free 8.0.7, Nginx 1.30.5, Node.js 24.21.0. Our project is afnodelab, its folder is /www/wwwroot/af-node-lab, and its domain is node.adminslab.com. Replace the domain and server IP with your own. Tested on 14 October 2026.
1. Install the runtime first
Complete Install Node.js LTS in aaPanel and verify Node and npm. You should have a supported runtime available in Website → Node.js Project. This example has no third-party dependencies and does not need a database.
2. Create a separate application folder
In aaPanel Files, open /www/wwwroot and create a directory named af-node-lab. Inside it create two files: server.js and package.json. Keep this sample in its own folder rather than replacing an existing website.
The package's start script tells the panel how to launch the app. The app listens on 127.0.0.1:3100, which is local to the VPS. Nginx will handle public website requests later. This simple example deliberately returns the same page for every request; it is a test app, not a complete production application.
Set the folder and files to owner www so the selected run user can read them. Standard directory permissions 755 and file permissions 644 are sufficient here; do not use 777 as a troubleshooting shortcut.
3. Add the application to aaPanel
Open Website → Node.js Project → Add Project → Default Project. Use the folder button beside Path to open the directory picker. Select af-node-lab under /www/wwwroot and confirm it.
Use these values:
Path: /www/wwwroot/af-node-lab
Project Name: afnodelab
Run User: www
Node.js runtime: v24.21.0, or your application's supported installed version
Start command: From package.json → start [node server.js]
Project Port: 3100
Install dependencies: Off for this dependency-free sample
Our panel displayed Yarn as the detected package manager. Because this sample has no dependencies, we left automatic dependency installation off. For a real app, select its intended package manager and follow its lockfile and build instructions.
Use a simple project identifier with letters, numbers or underscores. Our first identifier containing hyphens was rejected by the panel. The folder and package.json name can contain hyphens; the panel's project identifier has its own validation rule. Dismiss any error popup before correcting and resubmitting the form.
Click Confirm and wait for the project to show Running.
Final project configuration: app folder, start script, runtime, port and www user. The sample project is running and shows its process IDs and memory usage.
4. Verify the app locally
Before adding a domain, run these checks over SSH:
Code:
curl -i http://127.0.0.1:3100/
ss -ltnp | grep ':3100'
Expect HTTP 200 and the AdminsForum Node.js Lab page. The listening address should be 127.0.0.1:3100. We verified both in this lab. If the local request fails, fix the app startup before troubleshooting DNS or SSL.
5. Point a domain to the VPS
In Cloudflare DNS, create an A record for your chosen hostname. Our record was node → 217.217.235.82, with DNS only and automatic TTL. Check for conflicting records before adding it. Do not copy our IP for your own server.
We used DNS only while validating direct access to the origin. If you later enable Cloudflare proxying, check the origin certificate and Cloudflare SSL mode separately.
6. Add the domain and turn on Mapping
Click the project name to open its settings. In Domain Manager, enter node.adminslab.com and click Add. Add only a hostname, without https:// or a path.
Open Mapping, enable the switch and confirm. Mapping connects the hostname on the web server to the local Node.js service. Our HTTP domain check returned 200 after this stage. The project's port must match the listening port in server.js; changing one does not automatically change the other.
Mapping connects public web requests to the running local application.
7. Issue the HTTPS certificate
Open SSL → Let's Encrypt. Select File Verification and check your hostname, then click Apply. The DNS record must resolve to this VPS and HTTP validation must be reachable. Wait for certificate issuance and deployment to finish.
Select the mapped hostname for Let's Encrypt file verification.
Open https://node.adminslab.com in your browser. For your own installation, use your own hostname. Verify from the command line as well:
Code:
curl -I https://node.adminslab.com/
Do not add a certificate-validation bypass to make this check pass. Our request returned HTTP 200 with normal TLS validation enabled, and the browser displayed the expected page.
The Node.js sample page is accessible over HTTPS.
8. Check logs and restart the app
In project settings, Project log shows application startup output. Website log is for the mapped web server. These are different layers: an app can fail before Nginx can reach it.
Open Service status → Restart and confirm. We restarted only this sample project, then repeated the HTTPS request and confirmed that the expected page still loaded.
The application startup log confirms the local listening address.
The lab log also contained an npm warning about the global --init.module configuration. It did not prevent this sample from starting or passing the response checks. Read warnings as well as errors, but use the app's actual state and response to identify whether startup failed.
9. Common problems
502 Bad Gateway: Test localhost first. Check Running status, the start script, project port and Project log. Confirm Mapping points to the same port.
Module not found: A real app may need dependencies or a build step. Install them with the application's supported runtime and package manager; this sample needs neither.
Permission denied: Check the selected www user's access to the app files. Avoid running the app as root to hide a permissions issue.
SSL validation fails: Check DNS, HTTP reachability and redirects/CDN settings before retrying. A working local app does not prove that certificate validation can reach the hostname.
App stops after a VPS reboot: Review the Boot option in Project config and test recovery during a planned maintenance window. We tested an application restart, not a full VPS reboot; the lab Boot checkbox remained off.
Result: The sample app runs as www, listens only on localhost, responds through Nginx under its own domain, has working HTTPS and recovered after a project restart. This is a useful foundation before deploying a real application with dependencies, environment variables and a build process.
Install a supported Node.js runtime before adding a Node application to aaPanel. This guide covers the version manager, the runtime itself, the command-line selector and verification. Installing Node.js does not automatically publish an application or create an HTTPS website.
Tested setup
Ubuntu 22.04.5 LTS, aaPanel Free 8.0.7, Node.js version manager 2.3, Node.js v24.21.0 and npm 11.19.0. Tested on 14 October 2026. Menu labels and patch versions can change.
1. Check what you need
You need an existing aaPanel installation, administrator access to its dashboard and outbound connectivity for downloads. A domain is not required for installing the runtime. A domain and DNS will be needed later when publishing a website with HTTPS.
Node.js runs JavaScript on the server. npm manages JavaScript packages. A Node application is separate from a PHP website: changing a site's PHP version does not change its Node runtime.
2. Open the Node.js project page
Open Website → Node.js Project. On a new installation, aaPanel may display Node version manager is not installed. Click its Install link, then Install Now for the official version manager. Wait for Installation succeeded before continuing.
3. Choose a supported LTS release
Click Node version manager. Search for the runtime version you want, or use the version filters. In this lab we selected v24.21.0, the supported Node.js 24 LTS release available on the test date.
Check your application's supported Node versions first. The panel's Stable Version label alone does not mean a release is still maintained: its list also includes older releases. Check the official Node.js release schedule rather than choosing an old version just because it is marked stable.
Click Install in the matching row. The panel downloads the runtime and may then show a separate PM2 module installation stage. Let both finish; do not refresh while the progress window tells you to wait.
Node.js 24 LTS installation in the aaPanel version manager.
4. Confirm that installation finished
The installed row should offer Environment, Module and Uninstall controls instead of Install. This confirms that the manager recognises the runtime. Avoid clicking Uninstall while a project uses that version.
5. Set the command-line version
At the top of the manager, set Command line ver from Not set to your installed runtime. Wait for the environment-variable update to complete. This selects what a fresh shell uses for commands such as node and npm; a project's own runtime selection is a separate setting.
Close the old SSH session and reconnect before checking the result. An already-open terminal can retain its previous environment. On a server with existing Node projects, first check their runtime requirements before changing the default.
The installed runtime row and command-line version selection.
6. Verify Node.js and npm
In a fresh SSH login, run:
The path identifies the executable being used. The version commands identify the installed runtime and package manager. The final command confirms that Node can execute a simple JavaScript expression.
7. If node is still not found
Return to the manager and check that installation finished and Command line ver is not Not set. Reconnect your terminal, then retry. To distinguish a missing PATH setting from a failed installation, check the selected binary directly:
Code:
/www/server/nodejs/v24.21.0/bin/node --version
Replace the version in this path with your installed release. If the absolute path works but the plain node command does not, investigate the shell environment rather than reinstalling the application. If the file is missing, review the installation result and retry the failed download through the manager.
What comes next?
The runtime is ready. The next stage is creating an application directory, choosing its start command and runtime, verifying local output, and then mapping a domain through the web server with HTTPS. No application port needs to be opened publicly just to complete this installation guide.
CloudPanel vs aaPanel: an honest look from the CloudPanel side
CloudPanel is often called the clean, fast alternative to aaPanel. Is that true, and what do you give up? We ran both panels on the same 4 vCPU / 8 GB VPS and compared them hands-on. Here's what CloudPanel users (and people thinking of switching) should know.
The short answer
CloudPanel is the better pick for focused web hosting: PHP, WordPress, Node.js and Python sites, each with its own Linux user, on a tuned Nginx stack, completely free.
aaPanel is the better pick if you want one panel for the whole server (Docker, mail, FTP, 200+ apps) or you're on a tiny VPS.
Test setup
CloudPanel 2.5.4 on Ubuntu 24.04, and aaPanel 8.0.6 on Ubuntu 22.04 (its recommended release; 8.0.7 is now current). Same VPS, tested October 2026, with real screenshots throughout.
Where CloudPanel is different by design
CloudPanel doesn't try to manage everything. It installs a complete, tuned stack for you (Nginx, every PHP version from 7.1 to 8.5, MySQL 8.4 or MariaDB, Redis and Varnish) and gives you a calm interface with just Dashboard and Sites. aaPanel's dashboard packs in gauges, software lists, traffic graphs and a long tools menu.
Requirements: the one place CloudPanel asks for more
CloudPanel needs at least 2 GB RAM and 10 GB disk, and supports Ubuntu 22.04/24.04/26.04 and Debian 12/13 on x86 and ARM64. aaPanel starts at 512 MB (1 GB recommended) and supports more distros.
Install tip: verify the installer checksum from the CloudPanel docs, choose your DB engine (we used MySQL 8.4), then open
Code:
https://YOUR_IP:8443
and create the admin user immediately. Until it exists, the first visitor can create it. Better still, restrict port 8443 to your IP.
Hosting features, side by side
For everyday hosting the two are close: PHP versions, databases with one-click phpMyAdmin, free Let's Encrypt with auto-renew, a file manager and FTP.
What CloudPanel deliberately leaves out: there's no Docker manager, no mail server and no app store. Use an external email service, and run containers yourself behind a CloudPanel reverse-proxy site.
Memory: what CloudPanel actually uses We didn't run a controlled benchmark of the two panels, so no speed claims here. We did measure our CloudPanel server: two sites, a few hours after install, 1.95 GB used of 7.9 GB.
Most of it is MySQL's cache (~970 MB, sized to the server's RAM), plus PHP-FPM pools for every installed PHP version, Nginx and Varnish. That memory is put to work, but it's why CloudPanel wants 2 GB minimum. aaPanel installs only what you choose, so it can start smaller.
Security: isolation is CloudPanel's big win
Every CloudPanel site runs as its own Linux user. On our aaPanel server, site files were owned by the shared www user, so one compromised site has more reach there.
aaPanel has the bigger security toolbox (security centre, SSH controls, brute-force protection, intrusion detection, optional WAF, with more in Pro). CloudPanel covers the essentials: firewall rules, basic auth, IP and bot blocking, 2FA and Cloudflare-only mode, plus fail2ban out of the box.
Keep it updated: take a snapshot, then run
Code:
clp-update
. aaPanel updates from its Update button. Either way, OS updates and off-server backups are still on you. CloudPanel's remote backups (S3, Wasabi, Dropbox, Google Drive, SFTP and others) make the second part easy.
Price
CloudPanel is free, with no paid tiers. aaPanel's free edition also hosts sites fully, but aaPanel Pro costs $28.80/month, $198/year or $699 lifetime for the advanced WAF, anti-tamper, statistics and WordPress Pro tools.
Strengths and limitations
Should you choose CloudPanel? Yes, if you mainly host PHP, WordPress or Node.js sites, want every site isolated, run ARM servers or many client sites, and want nothing to buy, ever. Pick aaPanel instead if you need Docker, a mail server or lots of one-click apps, need Apache or OpenLiteSpeed, or have only 1 GB of RAM.