Deploying a Node.js application with Git and SSH creates a repeatable workflow: developers push version-controlled changes, the server retrieves an exact revision, dependencies are installed, and the application restarts. This guide covers that workflow for both cPanel-based Ruachost accounts and unmanaged Linux servers, with an emphasis on security, predictable releases, and straightforward rollback.
Introduction to Node.js Deployment#
A local Node.js application usually runs through a command such as npm start, listens on a development port, and reads configuration from local environment variables. Production deployment adds several responsibilities:
- Transferring application files without copying them manually
- Installing the correct Node.js version and dependencies
- Protecting credentials and private keys
- Keeping the application running after an SSH session closes
- Routing a domain to the application
- Serving traffic over HTTPS
- Monitoring errors and recovering from failed releases
Git tracks the application’s source and deployment history. SSH provides encrypted access to the server and can authenticate both administrators connecting to the server and the server connecting to a private Git repository.
The exact hosting environment affects how these responsibilities are handled.
Environment | Node.js management | App restart | Web routing |
|---|---|---|---|
cPanel hosting | Node.js Selector | cPanel interface | Managed through cPanel |
Unmanaged VPS | System packages or version manager | systemd or PM2 | Reverse proxy configuration |
Dedicated server | Administrator controlled | systemd or PM2 | Administrator controlled |
On cPanel hosting, the control panel handles much of the application integration. An unmanaged server provides more control, but it also makes you responsible for security updates, process management, firewall rules, HTTPS, and the reverse proxy.
Setting Up Your Server Environment#
Before deploying, confirm that Git and an SSH client are available locally. They are commonly included with macOS and Linux. Recent Windows versions provide OpenSSH through PowerShell or Command Prompt.
Your repository should contain at least:
- A valid package.json
- A start script or documented startup file
- A lock file such as package-lock.json
- An .env.example file containing placeholders
- A .gitignore entry for .env, logs, build caches, and node_modules
Do not commit passwords, API tokens, database credentials, or private SSH keys. Production values should be supplied through cPanel’s application environment settings, protected environment files, or an appropriate secrets-management system.
Preparing cPanel hosting#
In cPanel, open Tools → Software → Setup Node.js App. Create an application and choose:
- A supported Node.js version
- Production mode
- An application root
- The application URL
- The startup file, such as app.js or server.js
Prefer a current Long-Term Support release compatible with your dependencies. Confirm compatibility before changing an existing application’s runtime version.
Keep the application root separate from public_html unless the project specifically requires otherwise. Public assets can be exposed intentionally, while source files and configuration remain outside the public document root.
Preparing an unmanaged server#
On an unmanaged VPS or dedicated server, update system packages and install Git, Node.js, npm, and any build tools required by native dependencies. Run node --version, npm --version, and git --version to verify the installation.
Create a restricted application user rather than running Node.js as root. For example, the application can live under /srv/myapp and be owned by a dedicated myapp account. Administrative commands can still be performed through sudo, but the application process should have only the permissions it needs.
A release-based layout improves reliability:
- /srv/myapp/releases stores individual releases.
- /srv/myapp/current points to the active release.
- A protected shared location stores persistent uploads or configuration.
This structure makes it possible to prepare a release before activating it and switch back if post-deployment testing fails.
Configuring SSH for Secure Access#
SSH keys are safer and more convenient for repeat deployments than repeatedly entering an account password. Generate an Ed25519 key on your trusted local computer with ssh-keygen -t ed25519. Add a strong passphrase, especially when the key belongs to a person rather than an automated deployment process.
The command creates:
- A private key, which must remain on the originating machine
- A public key, which can be placed on the server
For a Ruachost cPanel account, open Security → SSH Access, import the public key, and authorize it. Test access using ssh username@server-hostname. The exact hostname and SSH port are available in your hosting account details.
Protect SSH files carefully. The .ssh directory should normally use permission mode 700, while private key files should use 600. Broader permissions may cause the SSH client to reject a key and can expose it to other system users.
Server access keys and repository access keys serve different purposes:
Key type | Purpose | Recommended scope | Stored where |
|---|---|---|---|
User login key | Connect to the server | One administrator | Administrator’s device |
Deploy key | Read one repository | One app or repository | Deployment server |
Machine identity | Access several repositories | Deployment automation | Controlled server account |
Agent forwarding | Temporarily use a local identity | Interactive work | Local SSH agent |
For one private repository, a read-only deploy key usually provides the narrowest access. GitHub’s deploy-key documentation notes that deploy keys are attached directly to repositories, while agent forwarding uses a local identity through the remote connection. Avoid agent forwarding in unattended deployment scripts.
For multiple applications, assign separate keys and configure SSH host aliases with Host, IdentityFile, and IdentitiesOnly yes. If one key is compromised, it can then be revoked without interrupting every deployment.
Using Git for Version Control#
Before deploying, commit the application locally and push it to the intended branch. Production should come from a controlled branch or tagged commit—not uncommitted files on a developer’s computer.
On the server, clone the repository through its SSH URL. For an existing checkout, inspect the working tree with git status before pulling. Unexpected modified files are a warning that production has drifted away from version control.
Avoid editing tracked files directly on the server. Emergency server changes are easily lost during the next deployment and are difficult to review. Make the correction locally, test it, commit it, and deploy the resulting revision.
For predictable production releases, consider deploying a tag or commit hash instead of whatever happens to be at the tip of a branch. Record the active commit with git rev-parse HEAD; that identifier makes auditing and rollback considerably easier.
Git should track source code and dependency lock files, but not generated runtime data. User uploads, logs, caches, database files, and secrets require persistent locations outside replaceable release directories.
Step-by-Step Deployment Process#
The following sequence works as a practical first deployment.
- Verify the application locally. Run its tests and production build. Confirm that the start command works and that the application reads its port and credentials from environment variables.
- Push a clean revision. Commit the intended files, review the changes, and push the production branch or tag to the remote repository.
- Connect to the server. Use your authorized SSH key and the command ssh username@server-hostname. Verify that you are logged in as the intended restricted user.
- Create the application location. On cPanel, use the application root selected in Node.js Selector. On an unmanaged server, create a new directory under the release path and assign it to the application user.
- Clone the repository. Use the repository’s SSH URL so a private repository can authenticate with its deploy key. Confirm the server’s Git-host fingerprint before accepting it.
- Select the intended revision. Check out the production branch, tag, or commit. Record the commit hash in deployment notes or logs.
- Install dependencies. Use npm ci when a lock file exists. Unlike a general dependency update, it installs the versions recorded in the lock file and fails if package.json and the lock file disagree.
- Build the application when required. TypeScript compilers and bundlers are often development dependencies. Install what the build needs, run npm run build, and then remove development-only packages with npm prune --omit=dev if the production process no longer requires them.
- Set environment variables. Add the database connection, session secret, API credentials, runtime mode, and other production values outside Git. Restrict access to any environment file.
- Activate and restart. In cPanel, restart the application through Node.js Selector. On an unmanaged server, update the current symlink and restart or reload the configured systemd or PM2 process.
- Configure public traffic. A self-managed server normally uses Nginx or Apache as a reverse proxy, forwarding HTTPS traffic to the application’s private local port. Obtain and renew a trusted TLS certificate rather than exposing the Node.js port publicly.
- Run post-deployment checks. Test the public URL, a health endpoint, authentication, database access, and any critical background task. Inspect logs before declaring the deployment complete.
For later deployments, create a fresh release instead of repeatedly modifying the active directory. Install and build there, activate it only after those steps succeed, and retain at least one known-good release for rollback.
Testing Your Node.js Application#
Testing should happen at several levels. A successful process restart only proves that a command ran; it does not prove the application is usable.
First, test the internal service on an unmanaged server by requesting its local address and port. Then test the public HTTPS address to verify DNS, TLS, reverse-proxy routing, and application behavior together.
A useful health endpoint should report whether the application process can serve requests. A deeper readiness check may also verify essential dependencies such as the database, but it should not reveal credentials, stack traces, or internal infrastructure details.
Check these production behaviors:
- The expected page or API response loads over HTTPS.
- HTTP redirects to HTTPS.
- Static assets return successfully.
- Database-backed requests work.
- Cookies have appropriate Secure and HttpOnly attributes.
- Invalid routes return controlled errors.
- Logs contain no repeated exceptions or missing-variable warnings.
Run tests from both a browser and a command-line HTTP client when possible. Browser testing reveals front-end, cookie, and cross-origin problems, while a direct request makes status codes and redirects easier to inspect.
Troubleshooting Common Deployment Issues#
Deployment failures usually come from authentication, permissions, runtime differences, or process configuration.
Symptom | Likely cause | Recommended action |
|---|---|---|
Permission denied (publickey) | Wrong or unauthorized SSH key | Check key authorization and selected identity |
Git clone fails | Deploy key or host alias is incorrect | Test repository SSH authentication |
npm ci fails | Lock file mismatch or Node incompatibility | Update the lock file locally and verify Node version |
Module not found | Dependencies or build output missing | Reinstall dependencies and rerun the build |
App exits immediately | Missing variable or startup error | Review application and process logs |
Domain shows an error | App port or proxy target is wrong | Confirm the listener and routing configuration |
Changes do not appear | Wrong branch or app not restarted | Check the commit hash and restart the app |
Access denied to files | Incorrect owner or mode | Restore ownership to the application user |
If the app works locally but not in production, compare Node.js versions, environment variable names, filename capitalization, working directories, and start scripts. Linux filesystems are commonly case-sensitive, so imports that differ only by capitalization may fail after working on a local machine.
For native npm modules, ensure the server has compatible system libraries and rebuild the package in the target environment. Do not upload a local node_modules directory from another operating system.
Best Practices for Future Deployments#
Once the first deployment works, make the process repeatable rather than relying on memory.
Keep a short deployment record containing the timestamp, operator, deployed commit, migration status, and result. Automate safe, deterministic steps, but retain clear failure handling. A deployment should stop if tests, dependency installation, or the build fails.
Additional safeguards include:
- Disable SSH password login after confirming key access, where you control the server.
- Restrict firewall rules to required services.
- Apply operating-system and Node.js security updates regularly.
- Give repository deploy keys read-only access unless write access is essential.
- Rotate and revoke keys when staff or infrastructure changes.
- Back up databases and persistent uploads separately from Git.
- Centralize logs and monitor availability, errors, memory, disk space, and certificate expiry.
- Test database migrations before production and design a rollback plan.
Deployments should also be idempotent: running the same release process twice should produce the same application state. Lock files, explicit Node.js versions, immutable release directories, and externalized configuration all support that goal.
Conclusion and Next Steps#
Git and SSH provide a secure foundation for Node.js deployment when each tool has a clear role: Git identifies the exact application revision, SSH protects access, npm installs locked dependencies, and the hosting environment keeps the process available. The most reliable workflow builds a fresh release, stores secrets outside the repository, runs the app as a restricted user, tests the public service, and preserves a rollback path.
Ready to deploy your application on a managed cPanel account or scalable server? Explore Ruachost and choose an environment suited to your Node.js project.



