Technology Encyclopedia Home >Set Up a Private npm Registry with Verdaccio on a Cloud Server — Host Internal Packages Securely

Set Up a Private npm Registry with Verdaccio on a Cloud Server — Host Internal Packages Securely

I had a utility library used in six different projects. Every time I updated it, I was manually copying files or maintaining a git submodule — both of which are annoying in their own ways. Publishing to public npm wasn't an option because the code was proprietary.

Verdaccio is the solution: a private npm registry that runs on your own server. Your team runs npm install @company/utils like any other package, it resolves from your registry, and public packages transparently proxy through to npm. It's the cleanest way to share internal JavaScript packages without exposing them publicly.

Setup is simpler than you might expect — Verdaccio is itself an npm package.

With Verdaccio running on a VPS, your team can npm install @company/utils just like any other package — except it pulls from your server, not the public internet. Public packages are proxied through to npm automatically, so you don't need to configure anything extra for external dependencies.

I run Verdaccio on Tencent Cloud Lighthouse. The entry-level plan handles Verdaccio comfortably — it's one of the lightest self-hosted services. The key advantage of running a private npm registry on a dedicated server: your team's packages are always available from any CI/CD pipeline or developer machine through a single, consistent URL (https://npm.yourdomain.com). Lighthouse's static public IP and stable infrastructure mean the registry stays available even during heavy CI build activity, unlike a registry running on a developer's local machine.


Table of Contents

  1. Why a Private npm Registry?
  2. What You Need
  3. Part 1: Install and Configure Verdaccio
  4. Part 2: Set Up Nginx with HTTPS
  5. Part 3: User Management
  6. Part 4: Publish Your First Package
  7. Part 5: Configure Clients to Use Your Registry
  8. Part 6: Scoped Packages and Proxy Configuration
  9. The Thing That Tripped Me Up
  10. Troubleshooting
  11. Summary

  • Key Takeaways
  • Use the appropriate Lighthouse application image to skip manual installation steps where available
  • Lighthouse snapshots provide one-click full-server backup before major changes
  • OrcaTerm browser terminal lets you manage the server from any device
  • CBS cloud disk expansion handles growing storage needs without server migration
  • Console-level firewall + UFW = two independent protection layers

Why a Private npm Registry? {#why}

A self-hosted npm registry makes sense when you:

  • Share internal packages across multiple projects without publishing them publicly
  • Cache npm packages to speed up installs and reduce external network dependency
  • Control package versions — lock your team to specific versions of internal dependencies
  • Audit package access — know who installed what and when
  • Work in air-gapped environments — serve packages locally without internet access

Verdaccio handles all these cases. It proxies public npm packages through to the real registry by default, so your team doesn't need to configure anything differently for external packages.


What You Need {#prerequisites}

Requirement Details
Server Ubuntu 22.04, 1 GB+ RAM
Node.js v18+
Domain For HTTPS setup
Ports 4873 (default Verdaccio) or 443 via Nginx

Part 1: Install and Configure Verdaccio {#part-1}

1.1 — Install Node.js

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt install -y nodejs

1.2 — Install Verdaccio

sudo npm install -g verdaccio

1.3 — Run Verdaccio Once to Generate Config

verdaccio
# Press Ctrl+C to stop after it generates the config

The default config is created at ~/.config/verdaccio/config.yaml.

1.4 — Review the Default Configuration

nano ~/.config/verdaccio/config.yaml

Key sections:

# Where packages are stored
storage: ~/.local/share/verdaccio/storage

# Authentication plugin
auth:
  htpasswd:
    file: ./htpasswd
    max_users: 1000

# Upstream registries
uplinks:
  npmjs:
    url: https://registry.npmjs.org/

packages:
  '@*/*':
    # Scoped packages - auth required to publish
    access: $all
    publish: $authenticated
    unpublish: $authenticated
    proxy: npmjs

  '**':
    # All other packages
    access: $all
    publish: $authenticated
    unpublish: $authenticated
    proxy: npmjs

# Restrict access to authenticated users only
web:
  enable: true
  title: Private npm Registry

To restrict all reads to authenticated users, change access: $all to access: $authenticated.

1.5 — Create a systemd Service

Create /etc/systemd/system/verdaccio.service:

sudo nano /etc/systemd/system/verdaccio.service
[Unit]
Description=Verdaccio private npm registry
After=network.target

[Service]
Type=simple
User=ubuntu
ExecStart=/usr/bin/verdaccio --config /home/ubuntu/.config/verdaccio/config.yaml
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable verdaccio
sudo systemctl start verdaccio
sudo systemctl status verdaccio

Part 2: Set Up Nginx with HTTPS {#part-2}

2.1 — Install Nginx and Certbot

sudo apt install -y nginx certbot python3-certbot-nginx

2.2 — Create Nginx Site

sudo nano /etc/nginx/sites-available/verdaccio
server {
    listen 80;
    server_name npm.yourdomain.com;

    location / {
        proxy_pass http://localhost:4873;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Allow large package uploads
        client_max_body_size 50m;
    }
}
sudo ln -s /etc/nginx/sites-available/verdaccio /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d npm.yourdomain.com

2.3 — Update Verdaccio Config with Your Domain

In ~/.config/verdaccio/config.yaml, add:

url_prefix: https://npm.yourdomain.com/

Restart:

sudo systemctl restart verdaccio

Part 3: User Management {#part-3}

3.1 — Add the First User

Users are managed via the htpasswd command or the npm CLI:

# On your local machine, against your registry
npm adduser --registry https://npm.yourdomain.com
# Follow prompts: username, password, email

Or add via htpasswd directly on the server:

sudo apt install -y apache2-utils
htpasswd -B ~/.config/verdaccio/htpasswd newuser

3.2 — Log In on Your Machine

npm login --registry https://npm.yourdomain.com
# Enter username and password

Verify:

npm whoami --registry https://npm.yourdomain.com

Part 4: Publish Your First Package {#part-4}

4.1 — Create a Simple Package

mkdir my-utils && cd my-utils
npm init -y

Edit package.json:

{
  "name": "@mycompany/utils",
  "version": "1.0.0",
  "description": "Internal utility functions",
  "main": "index.js",
  "publishConfig": {
    "registry": "https://npm.yourdomain.com"
  }
}

Create index.js:

exports.formatDate = (date) => {
  return new Date(date).toLocaleDateString('en-US');
};

exports.slugify = (str) => {
  return str.toLowerCase().replace(/\s+/g, '-');
};

4.2 — Publish the Package

npm publish --registry https://npm.yourdomain.com

You should see: + @mycompany/utils@1.0.0

4.3 — Browse the Web UI

Navigate to https://npm.yourdomain.com in a browser. Your package appears in the registry. The web UI shows package details, README, and version history.


Part 5: Configure Clients to Use Your Registry {#part-5}

Option A — Per-Project .npmrc

Add a .npmrc file to each project:

@mycompany:registry=https://npm.yourdomain.com/
//npm.yourdomain.com/:_authToken=YOUR_AUTH_TOKEN

Get your auth token from ~/.npmrc on your machine after logging in.

Now npm install @mycompany/utils resolves from your private registry. All other packages still come from the public npm registry.

Option B — Global npm Configuration

Set the scoped registry globally on each developer machine:

npm config set @mycompany:registry https://npm.yourdomain.com/

Option C — Use Verdaccio as the Full Registry

Route all packages through Verdaccio (it proxies public packages upstream):

npm config set registry https://npm.yourdomain.com/

This works well for teams in controlled environments where you want to cache all packages locally.


Part 6: Scoped Packages and Proxy Configuration {#part-6}

Restrict Specific Scopes to Your Registry

In config.yaml, packages are matched by pattern. Add a stricter rule for your company scope:

packages:
  '@mycompany/*':
    access: $authenticated
    publish: $authenticated
    unpublish: $authenticated
    # No proxy — these must come from your registry only
    
  '@*/*':
    access: $all
    publish: $authenticated
    proxy: npmjs

  '**':
    access: $all
    publish: $authenticated
    proxy: npmjs

With this config, @mycompany/ packages are never proxied to npm — they must exist in your registry.


The Thing That Tripped Me Up {#gotcha}

Publishing a new version of a package was failing with 403 Forbidden even though I was logged in.

The issue: Verdaccio's default htpasswd configuration limits registrations but also enforces package ownership. Once a user publishes a package, only that user can publish new versions by default.

I'd created a test user for initial testing and published the package. When I tried to publish with my real user, I got a 403.

The fix:

# On the server, edit the storage to change ownership
nano ~/.local/share/verdaccio/storage/@mycompany/utils/package.json

Find the _npmUser field and update it to your new username. Or simply delete the package and republish:

# Delete from Verdaccio storage
rm -rf ~/.local/share/verdaccio/storage/@mycompany/utils/
# Republish as the correct user
npm publish --registry https://npm.yourdomain.com

Going forward, keep consistent usernames and don't publish under test accounts you'll delete.


Troubleshooting {#troubleshooting}

Issue Likely Cause Fix
npm install returns 401 Not logged in npm login --registry https://npm.yourdomain.com
Package not found Scope not configured Add scope to .npmrc or npm config set @scope:registry
413 Request Entity Too Large Nginx body limit Set client_max_body_size 50m in Nginx config
Can't publish: 403 Package owned by different user Edit storage package.json or delete and republish
Public packages not resolving Proxy misconfigured Check uplinks.npmjs.url in Verdaccio config
Verdaccio not starting Port 4873 in use sudo lsof -i :4873; stop the conflicting process
Storage growing large Cached upstream packages Add max_fails: 10 to uplink config to limit caching

Summary {#verdict}

What you built:

  • Private npm registry accessible at https://npm.yourdomain.com
  • Publish and install internal scoped packages (@yourcompany/package-name)
  • Public packages transparently proxied through to npm
  • User authentication and access control
  • Web UI for browsing packages
  • Systemd service — starts automatically on server reboot

Teams using this setup stop copy-pasting utility code between projects and start treating shared libraries as proper versioned packages.

Frequently Asked Questions {#faq}

Is self-hosted Verdaccio suitable for production use?
Yes — Verdaccio is used in production environments ranging from individual developers to small teams. Pair it with regular Lighthouse snapshots and stay current with updates.

How do I migrate from a cloud-hosted Verdaccio to self-hosted?
Export your data from the cloud service, import it to the self-hosted instance, update DNS or internal service configurations, and verify everything works before switching fully.

How much disk space does Verdaccio need?
Initial installation is minimal. Disk usage grows with usage — artifacts, repositories, and caches accumulate over time. Monitor with df -h and use CBS cloud disk expansion when needed.

How do I set up backups for Verdaccio?
Use Lighthouse snapshots for full-server recovery. Additionally, export Verdaccio's application data directly (usually a backup command or data directory export) for granular restore capability.

Can I run Verdaccio alongside other services on the same server?
Yes, with sufficient RAM. Check the resource requirements and monitor actual usage. Containerized deployments (Docker) provide good isolation between services.

👉 Get started with Tencent Cloud Lighthouse
👉 View current pricing and launch promotions
👉 Explore all active deals and offers