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
| 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. Any registered actions appear as buttons in the Actions panel.
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 you have muted a specific process via the megaphone icon (📢) in the sidebar, alerts for that process are 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 Settings button in the sidebar toolbar to open the settings overlay.
- General - edit raw
.envkey/value pairs and change 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.
Every monitored process shows a megaphone icon (📢) in the sidebar. Click it to mute alerts for that process - the icon dims to indicate the muted state. Click again to re-enable. The preference is stored in the database and survives restarts.
- 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

