npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ytdlp-cookie-keeper

v0.1.0

Published

Keep your yt-dlp YouTube cookies fresh on a headless server: export from a persistent browser profile, validate against real videos, atomically swap — never clobber a working cookie file with a broken one.

Readme

ytdlp-cookie-keeper

Keep your yt-dlp YouTube cookies fresh on a headless server — export from a persistent browser profile, validate against real videos, swap atomically. Never clobber a working cookie file with a broken one.

The problem

Running yt-dlp on a server, you've probably seen this:

ERROR: Sign in to confirm you're not a bot. Use --cookies-from-browser or --cookies ...

So you export cookies from a logged-in browser and pass --cookies cookies.txt. It works — until YouTube rotates the session and the file goes stale, usually within days. The obvious fix, re-exporting on a cron with --cookies-from-browser pointed at a browser profile on the server, has a nasty failure mode: the moment the browser session dies, the export silently produces logged-out cookies and overwrites the working file you had. Playback breaks at the worst possible time and you don't find out until it does.

ytdlp-cookie-keeper closes that gap. Every refresh cycle:

  1. Exports cookies from a persistent browser profile into a temp candidate file (yt-dlp --cookies-from-browser).
  2. Validates the candidate end-to-end: yt-dlp must successfully extract real watch URLs using only the candidate.
  3. Swaps the live file in place (same path, same inode, 0600 perms) — only after validation passes.

A candidate that fails export or validation is discarded and your previous cookie file stays untouched. A stale-but-working file beats a fresh-but-broken one.

Extras you'd otherwise reimplement: a cooldown window so error-triggered refreshes can't stampede, in-flight deduplication so concurrent triggers join one refresh, and log/error redaction so cookie values and signed CDN URLs never leak into your logs.

Requirements

  • Node.js ≥ 20 (or Bun)
  • A recent yt-dlp on PATH (YouTube extraction increasingly needs a JS runtime — install yt-dlp[default] via pip, or have Deno/Node available for its challenge solver)
  • A browser installed on the server with a logged-in YouTube session in a persistent profile (see setup)

CLI

npm install -g ytdlp-cookie-keeper   # or: npx ytdlp-cookie-keeper ...

One-shot refresh (cron-friendly)

ytdlp-cookie-keeper refresh \
  --cookies /srv/secrets/youtube_cookies.txt \
  --browser chrome+basictext:/srv/youtube-browser-profile

Exit 0 on success, 1 on failure. The --browser value is passed straight to yt-dlp's --cookies-from-browser — the +basictext selects Chrome's plaintext keyring, typical for a dedicated headless-server profile.

Daemon

ytdlp-cookie-keeper daemon \
  --cookies /srv/secrets/youtube_cookies.txt \
  --browser chrome+basictext:/srv/youtube-browser-profile \
  --interval 6h --refresh-on-start

Freshness check (alerting)

The refresh loop only rewrites the file on success — so if the browser session dies, failures are silent and the file just quietly ages. Alert on that from anywhere (cron, CI, a monitoring box):

ytdlp-cookie-keeper check --cookies /srv/secrets/youtube_cookies.txt --max-age 13h

| Exit code | Meaning | |---|---| | 0 | fresh | | 2 | file missing | | 3 | stale — refresh loop is failing; the browser profile likely needs a re-login |

A 13h threshold on a 6h interval tolerates one missed cycle without flapping.

Durations everywhere accept 90s, 5m, 6h; bare numbers mean seconds.

Library

import { CookieKeeper } from 'ytdlp-cookie-keeper';

const keeper = new CookieKeeper({
  cookiesFile: '/srv/secrets/youtube_cookies.txt',
  browserSpec: 'chrome+basictext:/srv/youtube-browser-profile',
  refreshIntervalMs: 6 * 60 * 60 * 1000,
});

keeper.on('refresh:success', (e) => metrics.gauge('cookie_last_refresh', e.timestampMs / 1000));
keeper.on('refresh:failure', (e) => logger.warn('cookie refresh failed', { error: e.error }));

keeper.start(); // background loop

// Or trigger on demand, e.g. when playback hits a 403:
const result = await keeper.refreshNow({ reason: 'playback-403' });

Concurrent refreshNow() calls join the in-flight refresh; calls inside the cooldown window (default 5m) are skipped unless { force: true }. Failure messages are redacted (no URLs, no cookie values) and safe to log.

Setting up the persistent browser profile

The keeper refreshes cookies from a browser profile; keeping that profile's Google session alive is the part you own. A recipe for a Linux VPS:

# 1. Virtual display + Chrome with a dedicated profile dir
apt install xvfb x11vnc chromium xdotool
Xvfb :94 -screen 0 1280x800x24 &
DISPLAY=:94 chromium --user-data-dir=/srv/youtube-browser-profile \
  --password-store=basic --no-first-run &

# 2. Log in interactively, once, over VNC
x11vnc -display :94 -localhost -once
# tunnel: ssh -L 5900:localhost:5900 you@server, then VNC to localhost:5900
# and sign in to YouTube in the Chrome window

# 3. Point the keeper at the profile
ytdlp-cookie-keeper refresh \
  --cookies /srv/secrets/youtube_cookies.txt \
  --browser chrome+basictext:/srv/youtube-browser-profile

Tips that make the session last:

  • Use a throwaway Google account, not your own — automated-looking activity can trip account security.
  • Keep Chrome running (or restart it periodically) so its background token refresh keeps the session warm; a profile that's never opened goes stale faster.
  • --password-store=basic at first launch matches the +basictext keyring in the browser spec — cookies stay decryptable without a desktop keyring.
  • When Google eventually forces a re-login (weeks to months), log in manually over VNC again. Don't script the login: Google actively detects automated sign-ins, and CAPTCHA/2FA challenges can't be bypassed — a typed-credentials script fails exactly when you need it and puts the account at risk.

Recipes

systemd

# /etc/systemd/system/cookie-keeper.service
[Unit]
Description=Keep yt-dlp YouTube cookies fresh
After=network-online.target

[Service]
ExecStart=/usr/bin/ytdlp-cookie-keeper daemon \
  --cookies /srv/secrets/youtube_cookies.txt \
  --browser chrome+basictext:/srv/youtube-browser-profile \
  --interval 6h --refresh-on-start
Restart=on-failure
User=ytdlp

[Install]
WantedBy=multi-user.target

GitHub Actions staleness alert

name: Cookie Health
on:
  schedule: [{ cron: '0 */6 * * *' }]
  workflow_dispatch:
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Check cookie freshness on the server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USER }}
          key: ${{ secrets.SSH_KEY }}
          script: npx ytdlp-cookie-keeper check --cookies /srv/secrets/youtube_cookies.txt --max-age 13h
      - name: Alert on stale cookies
        if: failure()
        run: |
          curl -s -X POST "${{ secrets.WEBHOOK_URL }}" -H 'Content-Type: application/json' \
            -d '{"text":"YouTube cookies are stale — the browser profile likely needs a manual re-login."}'

Docker

Run the daemon as a sidecar sharing a secrets/ volume with whatever consumes the cookie file; mount the browser profile read-only into the sidecar. The consumer only ever sees a validated file.

Scope and non-goals

  • No login automation. This tool assumes a working browser session and keeps cookies flowing from it; establishing the session is a manual, interactive step by design (see above).
  • Not a bypass. If YouTube challenges the account, the validation step fails closed and your previous cookie file is preserved — nothing here evades bot detection; it just stops session rot from breaking you silently.
  • YouTube-flavored defaults, but the export→validate→swap pattern is site-agnostic: point --validate-url at any site yt-dlp supports.

Provenance

Extracted from FM-Hachimi, a Discord music bot that has run this refresh loop unattended in production. MIT.