A modern, real-time web dashboard for your PM2 processes.
Monitor, restart, and tail logs - all from a sleek dark-mode UI.
- Live process overview - CPU, memory, uptime, and restart count at a glance
- Real-time log streaming - stdout & stderr tailed directly in the browser
- Persistent monitoring - CPU/memory history and logs stored in SQLite, survives page reloads
- One-click restart - restart any process with an inline confirmation
- PM2 custom actions - trigger any
axm_actionsyour processes expose - Secure by default - scrypt password hashing, CSRF protection, rate limiting, CSP headers
- Single WebSocket connection - no polling, no SSE; all real-time data over one multiplexed stream
- Alerting - webhook and ntfy notifications when monitored processes log errors, with per-process mute and throttle support
- Dark-mode UI - clean, responsive dashboard that works on desktop and mobile
Pick a process in the sidebar, then work on it in one of three tabs. Every per-process function lives in exactly one of them.
| Tab | What it holds |
|---|---|
| Logs | Merged stdout and stderr, level filters, search, pause, copy, download |
| Metrics | CPU and memory history for the process, next to host CPU, RAM, and disk |
| Manage | Monitoring, per-process alerts, custom actions, deployment config, delete |
Restart and Stop/Start sit in the process header, alongside a live strip of CPU, memory, restart count, and uptime that stays visible on every tab.
| Login | Main View |
|---|---|
![]() |
![]() |
PM2-Hawkeye has two modes for each process.
Live only (default)
When you open a process without enabling monitoring, you see real-time CPU, memory, and a live log stream. None of this is saved. Navigating away or closing the tab loses the history - when you return you start fresh.
Monitored
Click Start Monitoring on any process to enable persistent tracking. PM2-Hawkeye will:
- Sample CPU and memory every 20 seconds and store the history (default: 24 hours)
- Persist incoming log lines to the database (default: 14 days)
- Show a sparkline chart of CPU and memory over time
- Backfill the last log entries from the PM2 log files on first enable
Monitoring state survives server restarts. You can stop monitoring at any time - this removes all stored history for that process.
- Node.js 22 or higher
- PM2 installed globally (
npm i -g pm2) - At least one PM2 process running
git clone https://github.com/orangecoding/pm2-hawkeye.git
cd pm2-hawkeye
yarn installCopy the example env file and open it in your editor:
cp .env.example .envGenerate a password hash and paste the output into .env:
node -e "
const crypto = require('crypto');
const salt = crypto.randomBytes(16).toString('hex');
const hash = crypto.scryptSync('YOUR_PASSWORD', Buffer.from(salt,'hex'), 64).toString('hex');
console.log('AUTH_PASSWORD_SALT=' + salt);
console.log('AUTH_PASSWORD_HASH=' + hash);
"npm startOpen http://localhost:3030 in your browser.
The recommended production setup is to run PM2-Hawkeye as a PM2 process itself so it is supervised and auto-restarted:
npm run build
pm2 start lib/transport/server.js --name pm2-hawkeye
pm2 saveupdate.sh handles the full update cycle for a self-hosted installation:
./update.shIt will:
- Pull the latest commits from upstream (fast-forward only - aborts if there are local changes)
- Install or update Node.js dependencies
- Rebuild the frontend bundle
- Restart the
pm2-hawkeyeprocess under PM2 (if running)
Make the script executable once before first use:
chmod +x update.shIf PM2-Hawkeye is not running under PM2 when the script finishes, it will print the commands to start it manually.
Note
Database location - by default the database is written to ./data/pm2-hawkeye.db inside the project directory. If you update by replacing the project folder (e.g. re-cloning), this file will be lost. Point SQLITE_DB_PATH at a directory outside the project to keep your data safe across updates (pm2-hawkeye.db is created inside it automatically):
SQLITE_DB_PATH=/var/lib/pm2-hawkeyeCreate the directory first: mkdir -p /var/lib/pm2-hawkeye
Note
PM2 timestamps - PM2-Hawkeye merges stdout and stderr and sorts log lines chronologically. This requires PM2 to prefix each line with a timestamp. Always start your processes with --time:
pm2 start app.js --name my-app --timeWithout it, log lines from stdout and stderr cannot be sorted correctly.
All settings are read from a .env file in the project root. Copy .env.example to get started.
Server
| Variable | Default | Description |
|---|---|---|
HOST |
0.0.0.0 |
Bind address |
PORT |
3030 |
HTTP and WebSocket port |
Authentication
| Variable | Default | Description |
|---|---|---|
AUTH_USERNAME |
admin |
Login username (case-insensitive) |
AUTH_PASSWORD_SALT |
- | Hex-encoded salt (generated above) |
AUTH_PASSWORD_HASH |
- | Hex-encoded scrypt hash (64 bytes) |
SESSION_TTL_MS |
28800000 |
Session lifetime in ms (default: 8 h) |
Rate limiting
| Variable | Default | Description |
|---|---|---|
AUTH_MIN_RESPONSE_MS |
900 |
Minimum login response time (timing-attack mitigation) |
LOGIN_WINDOW_MS |
600000 |
Sliding window for login attempts (10 min) |
LOGIN_MAX_REQUESTS |
12 |
Max login attempts per window |
LOGIN_FAILURE_WINDOW_MS |
1800000 |
Window for exponential lockout (30 min) |
LOGIN_BASE_LOCKOUT_MS |
30000 |
Initial lockout after repeated failures (30 s) |
LOGIN_MAX_LOCKOUT_MS |
43200000 |
Maximum lockout period (12 h) |
UNAUTH_WINDOW_MS |
60000 |
Window for unauthenticated access attempts (1 min) |
UNAUTH_MAX_REQUESTS |
10 |
Max unauthenticated hits before a 5 s penalty |
UNAUTH_PENALTY_MS |
5000 |
Delay added to rate-limited unauthenticated responses |
Cookies & proxy
| Variable | Default | Description |
|---|---|---|
COOKIE_SECURE |
auto |
auto / always / never |
TRUST_PROXY |
0 |
Set to 1 when behind a reverse proxy |
Storage
| Variable | Default | Description |
|---|---|---|
SQLITE_DB_PATH |
./data |
Directory for the SQLite database (pm2-hawkeye.db is created inside) |
METRICS_RETENTION_MS |
86400000 |
How long metric samples are kept (24 h) |
LOGS_RETENTION_MS |
1209600000 |
How long log entries are kept (14 days) |
MAX_LOG_BYTES_PER_FILE |
5242880 |
Max bytes read per PM2 log file (5 MB) |
PM2-Hawkeye can display and trigger custom actions that your app exposes to PM2 via tx2.
npm install tx2import tx2 from 'tx2';
// Simple action
tx2.action('clear cache', (done) => {
myCache.flush();
done({ success: true });
});
// Action with a parameter
tx2.action('set log level', (level, done) => {
logger.setLevel(level);
done({ level });
});done() must always be called - it signals to PM2 that the action has completed and sends the return value back to the dashboard.
Once your process is running, open it in PM2-Hawkeye and go to the Manage tab. Any registered actions are listed there, each with its own Run button. Actions declared with arity: 1 ask for their parameter before running.
Available tx2 APIs
| API | Purpose |
|---|---|
tx2.action(name, fn) |
Register a triggerable action |
tx2.action(name, { arity: 1 }, fn) |
Action that accepts a parameter |
tx2.metric(name, fn) |
Expose a live metric |
tx2.counter(name) |
Incrementing counter |
tx2.histogram(name) |
Value distribution histogram |
PM2-Hawkeye uses a two-process dev setup: the Node.js backend runs on port 3030 and an esbuild dev server handles the frontend on port 3042 with instant rebuilds and hot module replacement.
node --inspect lib/transport/server.jsnpm run devOpen http://localhost:3042. The dev server proxies all /api/*, /ws/*, and HTML routes through to the backend.
npm test
npm run lint
npm run format # write
npm run format:check # check onlyPM2-Hawkeye can send you a notification whenever a monitored process logs something that matches your configured severity threshold - for example, every time an error is written to stderr.
Log lines captured from your PM2 processes flow through a detection pipeline:
- Level detection - each line is inspected for a severity prefix (
ERROR,WARN,INFO,DEBUG). Lines arriving on stderr that carry no detectable prefix are automatically treated aserror. - Threshold check - the detected level is compared against the set of levels you selected in Settings. If the level is not in the set, the line is silently discarded.
- Per-process mute check - if alerts are switched off for that process in its Manage tab, the alert is skipped.
- Throttle check - in throttle mode a second alert for the same process is suppressed until the configured cool-down window has elapsed. In every match mode all qualifying lines trigger a notification.
- Dispatch - all enabled reporters are called concurrently. A failure in one reporter does not block the others.
Click the gear icon in the top bar to open the settings overlay.
- General - your
.envvalues, grouped by concern (server, sign in, brute-force protection, retention, deployments) with the raw key shown under each field. Also changes your login password. Changes are written to disk; a restart of PM2-Hawkeye is required for them to take effect. - Alerting - configure the alert mode, log level thresholds, and reporters.
Sends an HTTP POST request to any URL you choose. You can attach custom request headers and build a fully custom JSON body using key/value pairs with template variables:
| Variable | Replaced with |
|---|---|
{logLevel} |
Detected log level (error, warn, info, debug) |
{log_message} |
The full log line text |
{process_name} |
The PM2 process name |
Variable substitution is JSON-aware: if a value contains {log_message} inside a quoted JSON string it is substituted as a raw string; if it appears as an unquoted JSON value the substituted text is JSON-encoded automatically, preventing malformed payloads.
A live curl preview updates in real time as you fill in the form so you can verify the exact request before saving.
Sends a push notification to an ntfy topic. Configure the server URL, topic, message priority, and an optional auth token. PM2-Hawkeye sets the Title, Priority, and Tags headers automatically.
Open a monitored process and go to its Manage tab. The Alerts switch under Monitoring mutes and unmutes notifications for that process alone. The preference is stored in the database and survives restarts.
PM2-Hawkeye can clone, install, build, and register new Node.js applications directly from the UI -- no manual SSH required.
Click Deploy in the top bar to open the deploy form. Four fields are required (app name, branch, repository URL, start script); everything else is folded into collapsible groups that show their current value on the summary line. Hit Deploy and PM2-Hawkeye will:
- Run an optional pre-setup shell script (e.g. install system dependencies)
git clonethe repository intoDEPLOY_BASE_DIR/<app-name>(default:./apps/<app-name>)- Run the install command (
npm install,yarn, orpnpm install) - Run an optional build command (e.g.
npm run build) - Run an optional post-setup shell script (e.g. run database migrations)
- Register and start the process with PM2
A real-time progress log streams each step in the browser.
Repository (required)
| Field | Description |
|---|---|
| App name | Unique PM2 process name. Alphanumeric, dashes, and underscores only. |
| Repo URL | HTTPS URL of a public GitHub or GitLab repository. |
| Branch | Git branch to clone and deploy from. Defaults to main. |
Runtime (required)
| Field | Description |
|---|---|
| Start script | Entry point relative to the repo root, e.g. src/server.js. |
| Interpreter | Node.js interpreter path. Leave as node for the system default. |
| Exec mode | fork (single process) or cluster (multiple workers). |
| Instances | Number of cluster workers. Set -1 to use all CPU cores. |
Setup (optional)
| Field | Description |
|---|---|
| Install command | Package manager command. Select skip to skip dependency installation entirely. |
| Build command | Optional build step, e.g. npm run build. |
| Pre-setup script | Shell script that runs in DEPLOY_BASE_DIR before cloning. Use it to check requirements or install OS packages. |
| Post-setup script | Shell script that runs in the cloned repo directory after the build. Use it for migrations, file permission fixes, etc. |
Environment (optional)
| Field | Description |
|---|---|
| Env file | Path to a .env file inside the repo to load at start time, e.g. .env.production. |
| Environment variables | Key/value pairs passed directly to the process. These override values from the env file. |
Further collapsible groups cover how PM2 runs the app (interpreter, exec mode, instances), crash recovery (auto-restart, memory limits, scheduled restarts, ready signals), and logging and file watching.
A process that PM2-Hawkeye deployed is marked as such in two places: a git icon next to its name in the sidebar, and a chip in its header showing the repository and branch it came from. Both a Redeploy button in the header and the chip itself open the saved configuration, pre-filled.
In that form, Save and redeploy saves and immediately runs git pull --rebase, reinstall, rebuild, and PM2 restart. Save only persists the configuration without touching the running process. The same Redeploy button also sits in the Manage tab, next to the repository URL and the time of the last successful deploy.
Deployments whose PM2 process is gone appear under Not deployed in the sidebar with their own Redeploy and Delete buttons.
Set DEPLOY_BASE_DIR in your .env to control where apps are cloned:
DEPLOY_BASE_DIR=./apps
The directory is created automatically on first deploy. Use an absolute path to place apps outside the project directory.
- CSRF tokens - one-time-use tokens rotated after every state-changing request
- Rate limiting - sliding window + exponential backoff on failed logins; separate rate limit for unauthenticated access
- CSP headers - strict Content-Security-Policy including
ws:/wss:for WebSockets - Timing-attack mitigation - constant-time credential comparison with a minimum response delay
- Secure cookies -
HttpOnly,SameSite=Strict, optionalSecureflag
I maintain this and other open-source projects in my free time. If you find it useful, consider supporting the project.
Apache-2.0 - © Christian Kellner

