Half of a Beautiful Mind — thoughts, reflections, and the occasional letter.

How I Set Up Paperclip With Portless and PM2 — And Made It Survive Reboots

A practical guide to running a Node.js app as an always-on local service with clean .localhost URLs on macOS. If you’re self-hosting a Node.js project on your personal machine and…

Read more Copy link Cheer 2

A practical guide to running a Node.js app as an always-on local service with clean .localhost URLs on macOS.


If you’re self-hosting a Node.js project on your personal machine and you’re tired of remembering port numbers, restarting crashed processes, and manually launching things after every reboot — this post is for you.

I recently went through the full journey of getting Paperclip running permanently on my MacBook using PM2 (a Node.js process manager) and Portless (Vercel’s local reverse proxy tool). Along the way I hit heap memory crashes, shell script interpretation bugs, 404 ghost pages, and HTTPS certificate headaches. Here’s the distilled version so you don’t have to.


What We’re Building

By the end of this guide, you’ll have:

  • Your Node.js app running as a background daemon via PM2
  • A clean URL like https://paperclip.localhost instead of http://localhost:3100
  • Auto-restart on crashes
  • Auto-start on macOS boot — for both PM2 and the Portless proxy
  • HTTPS with locally trusted certificates

Prerequisites

You’ll need Node.js (I’m using v22 via Homebrew), pnpm (if your project uses it), and a macOS machine. The concepts translate to Linux with minor tweaks to the boot persistence step.


Step 1: Build Your Project

PM2 should run the built version of your server, not the dev script. Development scripts spawn child processes, use live reload, and behave unpredictably under a process manager.

cd /path/to/your/project
pnpm build

Verify the build works before involving PM2:

cd server && node dist/index.js

You should see something like Server listening on 127.0.0.1:3100. If it works manually, you’re ready for PM2.


Step 2: Install PM2 and Portless

npm install -g pm2
npm install -g portless

Portless must be installed globally — don’t add it as a project dependency or run it via npx.


Step 3: Start the Portless Proxy

Portless runs a lightweight reverse proxy on port 1355. When you enable HTTPS, it auto-generates certificates and trusts them on your system (prompts for sudo once on first run).

portless proxy start --https

After this, any app registered with portless becomes accessible at https://<name>.localhost. The proxy assigns each app a random port in the 4000–4999 range via the PORT environment variable, and most Node.js frameworks (Express, Next.js, Fastify) respect this automatically.


Step 4: Create the PM2 Ecosystem Config

This is where everything comes together. Create an ecosystem.config.cjs file in your project root:

// ecosystem.config.cjs
module.exports = {
  apps: ,
};

A few things to note here:

The script and args fields — instead of running tsx src/index.ts directly, we wrap it with portless paperclip, which registers the app with the proxy under the name “paperclip” and injects the PORT variable. The command portless paperclip pnpm exec tsx src/index.ts tells portless to register an app called “paperclip” and then run the remaining command.

NODE_OPTIONS: "--max-old-space-size=8192" — by default, Node.js caps heap memory at roughly 4 GB. If your app is memory-intensive (large datasets, heavy bundling, embedded databases), it can hit that ceiling and crash with FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. Bumping it to 8 GB gives breathing room.

max_memory_restart: "6G" — this tells PM2 to automatically restart the process if its RSS (resident set size) exceeds 6 GB. It acts as a safety net for memory leaks.

The PATH variable — PM2 doesn’t inherit your shell’s PATH. If you installed Node via Homebrew, you need to include the Homebrew paths explicitly so that pnpm, tsx, and portless can be found.


Step 5: Start PM2

Navigate to your project root (where ecosystem.config.cjs lives) and start:

cd /path/to/your/project
pm2 start ecosystem.config.cjs

Verify it’s running:

pm2 status

You should see your app listed with status online. Check the logs to confirm the server started and portless registered it:

pm2 logs paperclip --lines 30

Look for output indicating the server is listening. Then open your browser and visit:

  • https://paperclip.localhost — if you started portless with --https
  • http://paperclip.localhost:1355 — if you’re using HTTP

Step 6: Make Everything Survive Reboots

This is the part most tutorials skip. You want two things to start automatically when your Mac boots: the PM2 process list and the Portless proxy.

PM2 Boot Persistence

First, save the current process list so PM2 knows what to restore:

pm2 save

Then set up the startup hook:

pm2 startup

This command prints a sudo command specific to your system. Copy and run it. It creates a launchd service on macOS that starts PM2 on boot and restores your saved processes.

Portless Proxy Boot Persistence

If you’re running portless on port 1355 (the default, no sudo needed), you can manage it through PM2 itself:

pm2 start "portless proxy start --https" --name portless-proxy
pm2 save

If you need portless on port 443 (so https://paperclip.localhost works without a port number), it requires sudo, and you’ll need a launchd daemon instead. Create a plist file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.portless.proxy</string>
    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/bin/portless</string>
        <string>proxy</string>
        <string>start</string>
        <string>--https</string>
        <string>-p</string>
        <string>443</string>
        <string>--foreground</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>

Install it:

sudo cp com.portless.proxy.plist /Library/LaunchDaemons/
sudo launchctl load /Library/LaunchDaemons/com.portless.proxy.plist

Troubleshooting: The Pitfalls I Hit

The “JavaScript heap out of memory” crash

If you see a stack trace ending with FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed, your app is exceeding Node’s default ~4 GB heap. The fix is NODE_OPTIONS: "--max-old-space-size=8192" in your PM2 config. Monitor usage with pm2 monit and if memory keeps climbing, you may have a leak to track down.

PM2 isn’t picking up config changes

PM2 caches process configurations. If you edit ecosystem.config.cjs and just run pm2 restart paperclip, it may still use the old config. The reliable way to apply changes:

pm2 delete paperclip
pm2 start ecosystem.config.cjs

The SyntaxError: missing ) after argument list on tsx

This happens when PM2 tries to run a shell script (like node_modules/.bin/tsx) through the Node.js interpreter directly. The .bin/tsx file is a bash wrapper, not JavaScript. The fix: don’t set interpreter: "node" in your PM2 config when using portless as the script. Let PM2 use the default shell interpreter.

Portless shows 404 — “No apps running”

This means portless proxy is running but no app has registered with it. Common causes:

  • PM2 started before portless proxy — restart PM2 after portless is running
  • The app crashed on startup — check pm2 logs for errors
  • The portless command wasn’t found in PM2’s PATH — add Homebrew paths to the env.PATH in your config

HTTPS says “connection refused”

If https://paperclip.localhost refuses connections but http://paperclip.localhost:1355 works, portless is listening on 1355 but not on 443. The browser expects HTTPS on port 443 by default. Either access https://paperclip.localhost:1355, or restart portless on port 443 with sudo portless proxy start --https -p 443.

On first HTTPS visit, your browser may show a certificate warning since portless uses auto-generated certificates. Click through to accept it, or run sudo portless trust to add the CA to your system trust store permanently.


Monitoring and Management Cheat Sheet

# Check what's running
pm2 status

# Watch logs in real time
pm2 logs paperclip

# Real-time CPU and memory dashboard
pm2 monit

# Restart after config changes
pm2 delete paperclip && pm2 start ecosystem.config.cjs

# Stop without removing from PM2
pm2 stop paperclip

# Fully remove
pm2 delete paperclip

# Check portless proxy
portless proxy status

# List registered apps
portless list

The Final Result

After all of this, here’s what happens when I open my MacBook:

  1. macOS boots and launchd starts the Portless proxy (with HTTPS on port 443)
  2. launchd starts PM2, which restores the saved process list
  3. PM2 launches Paperclip wrapped in Portless, which registers it as paperclip.localhost
  4. I open https://paperclip.localhost in my browser and everything is just there

If the app crashes, PM2 restarts it automatically (up to 10 times, with a 5-second delay between attempts). If memory usage exceeds 6 GB, PM2 restarts it preemptively. I only need to intervene if I explicitly stop it myself.

No more localhost:3100. No more “is it running?” No more restarting things after a reboot. Just a clean URL that always works.


Have questions or ran into something I didn’t cover? Drop a comment or reach out — happy to help debug.

Comments 0
0 / 1000

My Dear B

So My Dear B, You’re off, by God! I can barely believe it since I am so unaccustomed to anybody leaving me save once. But reflectively I wonder why nobody…

Read more Copy link Cheer 1

So My Dear B,
You’re off, by God! I can barely believe it since I am so unaccustomed to anybody leaving me save once. But reflectively I wonder why nobody did so before, and the only one who did still came calling. All I care about — honest to God — is that you are happy and I don’t much care who you’ll find happiness with. I mean as long as he’s a friendly bloke and treats you nice and kind. If he doesn’t I’ll come at him with a hammer and clinker. God’s eye may be on the sparrow but my eye will always be on you. Never forget your strange virtues. Keep the royalty to your steps. Never forget that underneath that veneer of nonchallance is a remarkable and puritanical lady. I am a smashing bore or not? why you’d rather not have me and yet have me as a nobody is an indication of your sweetly confused loyalty.
I shall miss you with passion but no regrets.
Doors close Doors open.
This is an adaptation with Davidian Twists and Turns.

I don’t go around fire expecting not to sweat.

Comments 0
0 / 1000

Happy Independence Nigeria, 2017

Good morning and Happy Independence may we truly be Independent and emancipated from the Slavery of the mind. What is more important than this? If the mind is free. The…

Read more Copy link Cheer 1

Good morning and Happy Independence may we truly be Independent and emancipated from the Slavery of the mind. What is more important than this? If the mind is free. The physical will fall in place.

A mighty horse can easily be tied to “nothing” and yet remain immobile.

One of my greatest fears, confronted me in flesh and blood yesterday. A friend and university graduate actually insinuated a race is superior to the African and that’s me putting his assertions lightly.

Who has bewitched you? Who has bewitched this country?

I have come to conclude a lot of us even this my generation as educated as we seem are in a pitiable state of mind. Our minds have been beaten and battered into the shape society dictates, family dictates or just even friends.

If Nigeria will be great her youth must begin to stand like men and not bow to the mould handed to them. Reverence should be given whoever deserves such but do not be ashamed to take a stand where you should.

Do not follow in the steps of the past generation. If given the opportunity 80% of the young people I have met and interacted with say or imply they would probably be worse in that they would just loot the Treasury “smarter”. This only gives a bleak future. Unfortunately the future is now as I can already see some close to those positions and ready to implement their selfish ambitions.

Define your truths and define what’s falsehood for you, define your good and define your bad correlate this with what may be progressive you, family, friends and the country, choose to do the truth and the good. These areas of life (truth and good) are slippery, hold on to them like your life depends on it cause it actually does.

This cannot be over emphasized “Never be a Flag!!” You were born alone except you are a Siamese twin, even regular twins are alone, and except science out wits death soon you will die alone.. Know when to wield the spirit of comradeship and when to abandon ship.. Do not be pushed to follow leaders you do not understand their good and truths.

Be patriotic, loyal to family, stand with friends, protect your community.

A great Nigeria starts with you!

Comments 0
0 / 1000

One Help A Day

 365 Days of Helping. So I woke up this morning, yeah I woke up. Something to be happy about I guess. Grateful if you like. I have adopted a “morning…

Read more Copy link Cheer 0

 365 Days of Helping.

So I woke up this morning, yeah I woke up. Something to be happy about I guess. Grateful if you like. I have adopted a “morning ritual” of getting some positive messages into myself each morning via instagram particularly since well after my laptop (if its close by) the next thing I reach for is my phone. I forgot, my glasses actually comes first:

On one of those mornings I stumbled on this

The important thing is the CAN, Hence my thought was even if I am broke as a broken chewing stick I still have so much someone out there doesn’t have and may never have. Coincidentally saw this video this morning

Inspired by this I decided I could do something by offering an open cheque. So I made this post



Within minutes I got calls, comments and WhatsApp messages that lasted throughout the day. I won’t dwell much on the downsides but one thing was prevalent a lot of people define help == financial help. Help is not always financial.

After each person I was able to help I specifically mentioned it would be nice if they could do the same for at least someone else. At the end of the day, I slept better and was happy I could help even if it was a few. In discussing with some about the experience and of course the result I got personally, made a resolve I’d like to try this for the next 365 days. I know some people I have mentioned this to have decided they would give it a try as well.

Some things I learnt and think you should have in mind just in case you want to join in,

  • You don’t have to tell anyone what you did, you can judge yourself if what you did was worth it and not just a show by how you feel at the end of the day.
  • Encourage those you help to help others in like manner.
  • Job responsibilities are different from helping people. Don’t replace what you should do with some favour or good you are doing for someone.
  • What you do for livelihood is different from helping people, personally for me when my business does promos (price-slashes) I try to share with as many as possible to take advantage of it.
  • its important to separate this from some religious rite or seeking to please some super being, which you may do in the process, which is good. “Actions ultimately make the difference between living a good life and not living a good life.”
  • Things you could do, You can help a neighbour wash a car, you can help people get a message across,  you can use your skill/knowledge in a way that doesn’t hurt you. You can even give money, ONLY if you have that money and won’t go begging cause of that. These are not perfect examples but you get the idea. Do what you can, When you can as Often as you can. If you get requests beyond you, decline politely.

In case you are the kind that needs to be monitored or give yourself a “punishment” for not getting your goal done you may like this site

If you found this useful or a good read kindly share.
Here’s to a good year of Good! Cheers

Psst! and this

 

Comments 0
0 / 1000

Clutter and Progress

I think it’s safe to say clarity and simplicity bring along more progress than clutter would. Clean out my life, clean out your life of clutter; things, people, decisions that…

Read more Copy link Cheer 1

I think it’s safe to say clarity and simplicity bring along more progress than clutter would. Clean out my life, clean out your life of clutter; things, people, decisions that don’t necessarily serve a purpose. Let it go! Let them go!

Comments 0
0 / 1000