VideoScale Blog

yt-dlp Guide 2026: Install, Configure, Use Proxies & Scale YouTube Downloads

Developer Guides

yt-dlp Guide 2026: Install, Configure, Use Proxies & Scale YouTube Downloads

By Maya Chen · 2026-10-23

Important: Download only videos you have the right or authorization to download. You are responsible for complying with applicable copyright law, platform terms, privacy requirements, and other applicable rules.

yt-dlp is one of the most capable open-source tools for downloading and processing online video. What begins as a simple command-line utility can become a surprisingly sophisticated part of a video ingestion pipeline.

Running:

yt-dlp "VIDEO_URL"

is easy.

Running yt-dlp reliably across thousands or millions of URLs is a completely different engineering problem.

At scale, you need to think about extraction, JavaScript runtimes, FFmpeg, proxy infrastructure, retries, queues, storage, deduplication, observability, container isolation, concurrency limits, authentication, updates, and failures caused by upstream platform changes.

This guide covers both sides of the problem:

  1. how to install and use yt-dlp correctly in 2026;
  2. how to design a production architecture around it;
  3. how proxies fit into that architecture;
  4. how to containerize workers;
  5. how to organize downloaded media;
  6. how to test and monitor the system;
  7. and when it makes more sense to use a managed video-download service such as VideoScale instead of maintaining the entire stack yourself.

«Important: Download only videos you have the right or authorization to download. You are responsible for complying with applicable copyright law, platform terms, privacy requirements, and other applicable rules.»


Understand yt-dlp in 2026

What Is yt-dlp?

yt-dlp is a feature-rich command-line audio/video downloader supporting thousands of websites.

It originated as a fork of youtube-dl and has evolved into an actively maintained project with a large extractor ecosystem, extensive format-selection capabilities, metadata extraction, subtitles, thumbnails, authentication, post-processing, proxy support, browser impersonation, plugins, and integrations with external downloaders.

For YouTube specifically, yt-dlp can handle tasks including:

  • individual videos;
  • playlists;
  • channels;
  • Shorts;
  • live streams;
  • audio-only extraction;
  • subtitles;
  • automatically generated subtitles;
  • thumbnails;
  • metadata;
  • format selection;
  • separate audio/video streams;
  • FFmpeg merging;
  • cookies;
  • proxies;
  • output templates;
  • download archives;
  • filtering;
  • post-processing;
  • structured JSON metadata output.

That makes it useful both as an interactive CLI and as a building block for larger applications.


What Changed in yt-dlp in 2026?

One of the most important operational lessons with yt-dlp is that keeping it current is part of running it.

YouTube and other platforms change continuously. Extraction logic that works today can stop working after an upstream change.

As of August 22, 2026, the latest upstream release is 2026.08.19.

Recent development has included several changes worth understanding.

August 2026: YouTube Client and Extraction Improvements

The August 19 release added and adjusted several YouTube-related capabilities, including:

  • a "visionos" YouTube player client;
  • "web_embedded" client fallbacks;
  • updated player client versions;
  • player-client maintenance;
  • improvements to channel metadata extraction;
  • fixes for live adaptive fragment generation;
  • removal of "android_vr" from the default client set.

The same release also added support for "curl_cffi" 0.16.x and continued work around browser impersonation.

These changes illustrate why a production installation should not treat yt-dlp as a binary you install once and forget.


The July 2026 release raised the minimum recommended Python version to Python 3.11.

Python 3.10 was approaching end-of-life, and upstream indicated that support would be removed in the future.

For a new server deployment, therefore, Python 3.11+ is the sensible baseline.


JavaScript Runtime Requirements Matter More

Modern YouTube extraction increasingly involves JavaScript execution.

Current yt-dlp documentation strongly recommends:

  • "ffmpeg" / "ffprobe";
  • "yt-dlp-ejs" for full YouTube support;
  • a supported JavaScript runtime.

Deno is the recommended runtime.

Recent requirements raised the minimum Deno version to 2.3.0, while supported Node.js versions are 22+. Bun support has been deprecated and restricted to specific versions.

For a fresh deployment, use Deno unless you have a reason to standardize on Node.js.


Security Hardening

Several 2026 releases contained security changes affecting features including:

  • "--netrc-cmd";
  • "--exec";
  • external curl downloading;
  • aria2c;
  • filename handling;
  • shortcut/link output.

Notably, HLS/DASH downloading through aria2c was removed following a security issue. Native concurrent fragment downloading using "-N" is now the better direction for those workflows.

This matters for production systems because blindly copying old yt-dlp command examples from blog posts can introduce unnecessary risk.


Build the Ubuntu download stack

For a basic server, the stack looks like:

Application
     |
     v
yt-dlp
     |
     +---- Deno / JavaScript runtime
     |
     +---- FFmpeg / ffprobe
     |
     +---- Proxy
     |
     v
YouTube
     |
     v
Temporary Storage
     |
     v
Final Storage / Object Storage

At scale, a better architecture looks like:

                  +--------------------+
Client ---------->| API / Control Plane|
                  +---------+----------+
                            |
                            v
                  +--------------------+
                  |    Job Queue       |
                  | Redis / RabbitMQ   |
                  +---------+----------+
                            |
             +--------------+--------------+
             |              |              |
             v              v              v
         Worker 1        Worker 2       Worker N
             |              |              |
             +------- Proxy Layer ---------+
                            |
                            v
                         YouTube
                            |
                            v
                     Temporary Storage
                            |
                            v
                 S3 / R2 / MinIO / NAS

Each worker should ideally be disposable.

That is why containers are attractive for production deployments.


Installing yt-dlp on Ubuntu

The exact Ubuntu package versions depend on the Ubuntu release, but the following is a good general-purpose setup.

Start by updating the machine:

sudo apt update
sudo apt upgrade -y

Install basic utilities:

sudo apt install -y \

curl
wget
git
ca-certificates
gnupg
unzip
jq
ffmpeg
python3
python3-pip
python3-venv

Check Python:

python3 --version

For a new 2026 installation, target Python 3.11 or later.

Check FFmpeg:

ffmpeg -version

And:

ffprobe -version

Installing yt-dlp with pip

Using an isolated Python environment avoids polluting the system Python installation.

Create a directory:

sudo mkdir -p /opt/yt-dlp
sudo chown "$USER":"$USER" /opt/yt-dlp

cd /opt/yt-dlp

Create a virtual environment:

python3 -m venv venv

Activate it:

source venv/bin/activate

Upgrade packaging tools:

python -m pip install --upgrade pip setuptools wheel

Install yt-dlp with the recommended dependency set and browser-impersonation support:

python -m pip install -U "yt-dlp[default,curl-cffi]"

Check installation:

yt-dlp --version

Inspect verbose environment information:

yt-dlp --verbose

List extractors:

yt-dlp --list-extractors

List available browser impersonation targets:

yt-dlp --list-impersonate-targets

Installing Deno

For full modern YouTube support, provide a compatible JavaScript runtime.

Deno is currently the recommended choice.

After installing Deno using its official installation instructions, verify it with:

deno --version

Use Deno 2.3.0 or later with current yt-dlp versions.

If you prefer Node.js:

node --version

Current yt-dlp documentation requires supported Node versions to be Node 22 or newer.


Alternative: Official yt-dlp Binary

Linux users can also use the project's release binary.

A typical layout is:

sudo install -d /usr/local/bin

Place the official "yt-dlp" release there and make it executable:

sudo chmod a+rx /usr/local/bin/yt-dlp

Then:

yt-dlp --version

Official release binaries support:

yt-dlp -U
yt-dlp currently maintains three release channels:

stable nightly master

Interestingly, upstream currently recommends nightly for regular users, because websites change frequently and stable releases can become outdated between releases.

Switch to nightly with:

yt-dlp --update-to nightly

For production, however, consider testing a new build before deploying it across every worker.

A practical model is:

nightly | v canary worker | v integration tests | v production worker image


Basic yt-dlp Commands

Download a video:

yt-dlp "VIDEO_URL"

List available formats:

yt-dlp -F "VIDEO_URL"

Download the automatically selected best combination:

yt-dlp "VIDEO_URL"

Explicitly request best video plus best audio:

yt-dlp -f "bestvideo+bestaudio/best" "VIDEO_URL"

Prefer MP4-compatible streams:

yt-dlp \

-f "bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4]/best"
"VIDEO_URL"

Merge into MP4 when possible:

yt-dlp \

-f "bestvideo+bestaudio/best"
--merge-output-format mp4
"VIDEO_URL"


Limit Video Resolution

Up to 1080p:

yt-dlp \

-f "bv*[height<=1080]+ba/b[height<=1080]"
"VIDEO_URL"

Up to 720p:

yt-dlp \

-f "bv*[height<=720]+ba/b[height<=720]"
"VIDEO_URL"

This is useful when storage and bandwidth matter more than retaining the absolute highest available quality.


Audio Downloads

Extract audio:

yt-dlp -x "VIDEO_URL"

Convert to MP3:

yt-dlp \

-x
--audio-format mp3
"VIDEO_URL"

High-quality MP3:

yt-dlp \

-x
--audio-format mp3
--audio-quality 0
"VIDEO_URL"

Other supported audio conversion formats include:

aac alac flac m4a mp3 opus vorbis wav

FFmpeg and ffprobe are required for these post-processing workflows.


Metadata

Print metadata as JSON without downloading:

yt-dlp -j "VIDEO_URL"

Pretty-print it:

yt-dlp -j "VIDEO_URL" | jq .

For playlist-level structured information:

yt-dlp -J "PLAYLIST_URL"

Write metadata alongside the video:

yt-dlp \

--write-info-json
"VIDEO_URL"

For data pipelines, metadata extraction is often as important as the video itself.

You can store:

video.mp4 video.info.json video.jpg video.en.vtt

together as one logical media object.


Thumbnails

Download the thumbnail:

yt-dlp \

--write-thumbnail
"VIDEO_URL"

Convert thumbnails:

yt-dlp \

--write-thumbnail
--convert-thumbnails jpg
"VIDEO_URL"

Embed the thumbnail where supported:

yt-dlp \

--embed-thumbnail
"VIDEO_URL"


Subtitles

List subtitles:

yt-dlp --list-subs "VIDEO_URL"

Download subtitles:

yt-dlp \

--write-subs
"VIDEO_URL"

Download automatically generated subtitles:

yt-dlp \

--write-auto-subs
"VIDEO_URL"

Select English:

yt-dlp \

--write-subs
--sub-langs "en.*"
"VIDEO_URL"

Convert them to SRT:

yt-dlp \

--write-subs
--sub-langs "en.*"
--convert-subs srt
"VIDEO_URL"

For AI pipelines, subtitles can be particularly useful because they may eliminate the need to transcribe the entire audio stream yourself.


Output Templates

yt-dlp's output templating system is extremely powerful.

Simple example:

yt-dlp \

-o "%(title)s.%(ext)s"
"VIDEO_URL"

Include the video ID:

yt-dlp \

-o "%(title)s [%(id)s].%(ext)s"
"VIDEO_URL"

For a channel archive:

yt-dlp \

-o "%(channel)s/%(upload_date)s - %(title)s [%(id)s].%(ext)s"
"URL"

For large-scale ingestion, IDs are preferable to titles as canonical identifiers.

A good storage layout is:

/videos/ youtube/ CHANNEL_ID/ VIDEO_ID/ source.mp4 metadata.json thumbnail.jpg subtitles/

Titles can change.

YouTube video IDs are far better database/storage keys.


Download Archives and Deduplication

One of the most important yt-dlp options for recurring jobs is:

--download-archive

Example:

yt-dlp \

--download-archive /data/archive.txt
"CHANNEL_URL"

After successfully downloading an item, yt-dlp records it.

On future executions it can skip previously processed videos.

For production architectures, use both:

yt-dlp archive

database-level uniqueness

For example, your database can enforce:

UNIQUE(platform, video_id)

The database should be the authoritative source of truth; the archive can remain a useful worker-level optimization.


Date Filtering

Download videos after a particular date:

yt-dlp \

--dateafter 20260101
"CHANNEL_URL"

Before a date:

yt-dlp \

--datebefore 20260701
"CHANNEL_URL"

Relative date syntax is also supported.

This is useful for incremental channel ingestion.


Filtering

yt-dlp provides "--match-filters".

For example:

yt-dlp \

--match-filters "!is_live"
"URL"

Filters can reference output-template fields and support combinations of conditions.

At scale, however, consider separating:

metadata discovery | v business filtering | v download

rather than encoding every product rule into a giant shell command.


Using Cookies

Some content may require authentication or an established browser session.

yt-dlp supports:

--cookies FILE

For example:

yt-dlp \

--cookies /secure/cookies.txt
"VIDEO_URL"

It can also read cookies from supported browsers:

yt-dlp \

--cookies-from-browser chrome
"VIDEO_URL"

Supported browsers include Chrome, Chromium, Firefox, Edge, Brave, Opera, Safari, Vivaldi, and others.

Be extremely careful with cookies.

Treat a cookie file like a credential.

Never:

commit it to Git put it inside a public Docker image print it into application logs store it in an unsecured bucket send it to an AI coding assistant

Use a secrets manager or securely mounted secret instead.


Network, test, and protect downloads

Using Proxies with yt-dlp

yt-dlp has native proxy support:
yt-dlp \

--proxy "http://USER:PASSWORD@HOST:PORT"
"VIDEO_URL"

HTTPS-style proxy endpoint:

yt-dlp \

--proxy "https://USER:PASSWORD@HOST:PORT"
"VIDEO_URL"

SOCKS5:

yt-dlp \

--proxy "socks5://USER:PASSWORD@HOST:PORT"
"VIDEO_URL"

A proxy can also be used specifically for geo verification using:

--geo-verification-proxy

Do not hardcode proxy passwords into source code.

Use environment variables or a secret manager.

For example:

export YTDLP_PROXY='http://USER:PASSWORD@HOST:PORT'

Then:

yt-dlp \

--proxy "$YTDLP_PROXY"
"VIDEO_URL"


What Kind of Proxy Should You Use?

For small workloads, you may not need a proxy at all.

For distributed video-processing infrastructure, however, egress management becomes an architectural concern.

Typical proxy categories are:

Datacenter Static residential Rotating residential ISP/static ISP

Datacenter proxies are generally cheaper and faster, but their IP ranges are easier for platforms to classify as infrastructure traffic.

Residential proxies use residential-network IP addresses.

For workloads where you legitimately need distributed residential egress or geographic coverage, a provider such as "residential proxy" (https://privateproxy.me/residential-proxy) offers rotating and static residential endpoints.

PrivateProxy advertises rotating residential endpoints with IP rotation per request or timer, sticky sessions, HTTP(S) and SOCKS5 connectivity, and country/city targeting.

For long-running video downloads, session consistency matters. Rotating an IP in the middle of one transfer can break a session or produce inconsistent behavior.

Therefore, architect proxy selection around a job/session, not blindly around every HTTP request.

A useful abstraction is:

Download Job | v Proxy Assignment | +--- session / endpoint | v

yt-dlp worker

For longer downloads, sticky sessions or an appropriately stable endpoint are usually preferable to uncontrolled per-request rotation.


Proxy Rotation and Warming

At significant scale, do not let every worker invent its own proxy strategy.

Create a proxy-management layer:

              Proxy Manager
                   |
   +---------------+---------------+
   |               |               |
Pool A          Pool B          Pool C
   |               |               |

Worker 1 Worker 2 Worker 3

The proxy manager can track operational signals such as:

provider region session assigned_at success_count failure_count latency cooldown_until health

When using PrivateProxy rotating residential infrastructure, the provider performs IP rotation at the proxy-network layer according to the endpoint/session configuration. Your application can add another layer above it: assign sessions to jobs, test endpoint health, maintain cooldown policies, and avoid repeatedly sending failed jobs through the same route.

“Proxy warming” should not mean trying to disguise abusive automation. In a legitimate infrastructure context, it should mean conservative health validation before assigning production traffic: establish connectivity, verify expected region/egress behavior, and ramp workload gradually rather than immediately saturating a newly provisioned route.


Testing a Proxy

Before involving yt-dlp, validate connectivity independently.

For example:

curl \

--proxy "$YTDLP_PROXY" \

  https://example.com/

Then test yt-dlp without downloading:

yt-dlp \

--proxy "$YTDLP_PROXY"
--simulate
"VIDEO_URL"

For verbose diagnostics:

yt-dlp \

--verbose
--proxy "$YTDLP_PROXY"
--simulate
"VIDEO_URL"

Never paste verbose production logs publicly without checking them for credentials, cookies, URLs containing tokens, or other sensitive information.


Browser Impersonation

Modern anti-bot systems can consider TLS and HTTP fingerprints in addition to the User-Agent header.

yt-dlp supports request impersonation when the appropriate dependency is installed.

List targets:

yt-dlp --list-impersonate-targets

Example:

yt-dlp \

--impersonate chrome
"VIDEO_URL"

Upstream currently recommends "curl_cffi" for browser-request impersonation.

Install it through the yt-dlp extra:

pip install -U "yt-dlp[default,curl-cffi]"

Do not automatically force impersonation on every request. The yt-dlp documentation explicitly notes that doing so can hurt download speed or stability.


Concurrent Fragment Downloads

yt-dlp provides:

-N

for concurrent fragment downloads.

For example:

yt-dlp \

-N 4
"VIDEO_URL"

Higher is not automatically better.

If you have:

20 workers × 8 simultaneous videos × 8 fragments

you may unexpectedly create:

1,280 concurrent fragment requests

before accounting for metadata requests, retries, thumbnails, subtitles, and other traffic.

Concurrency therefore needs to be managed globally.


Rate Limiting

yt-dlp provides controls that can help prevent individual workers from consuming unlimited bandwidth.

At the infrastructure level, consider limiting:

jobs per worker fragments per job bandwidth per worker jobs per proxy session retry frequency global concurrency

The correct architecture is usually conservative and adaptive rather than simply maximizing parallelism.


Retries

Networks fail.

Proxy endpoints fail.

CDNs fail.

Videos disappear.

Your queue should assume that failures are normal.

A basic retry strategy might look like:

Attempt 1 | failure | wait | Attempt 2 | failure | longer wait | Attempt 3 | failure | dead-letter queue

Use exponential backoff with jitter at the orchestration layer.

Do not create infinite retry loops.


Testing yt-dlp Without Downloading

One of the most useful production health checks is:

yt-dlp \

--simulate
"VIDEO_URL"

You can inspect formats:

yt-dlp \

--simulate
--list-formats
"VIDEO_URL"

Metadata:

yt-dlp \

--dump-json
"VIDEO_URL"

Verbose diagnostics:

yt-dlp \

--verbose
--simulate
"VIDEO_URL"

A worker readiness test should verify:

yt-dlp executable

Python FFmpeg

ffprobe

JavaScript runtime DNS outbound HTTPS proxy connectivity if configured temporary storage final storage queue connection


Production Health-Check Script

Example:

#!/usr/bin/env bash

set -euo pipefail

echo "Checking yt-dlp..."

yt-dlp --version

echo "Checking FFmpeg..."

ffmpeg -version >/dev/null

echo "Checking ffprobe..."

ffprobe -version >/dev/null

echo "Checking Deno..."

deno --version

echo "Checking disk..."

df -h

echo "Checking yt-dlp extraction..."

yt-dlp \

--simulate
--no-warnings
"$HEALTHCHECK_VIDEO_URL"

echo "Worker healthy."

Use a video you are authorized to access as your health-check target.


Operate workers at scale

Dockerizing yt-dlp

For production-scale deployments, containers provide several advantages:

  • reproducible dependencies;
  • immutable workers;
  • easier rollbacks;
  • resource limits;
  • horizontal scaling;
  • consistent FFmpeg versions;
  • isolated updates.

A conceptual Dockerfile:

FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
RUN apt-get update \
&& apt-get install -y \
   ffmpeg \
   curl \
   ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir \
"yt-dlp[default,curl-cffi]"
WORKDIR /app
COPY worker.py /app/worker.py
RUN mkdir -p /downloads /tmp/yt-dlp
CMD ["python", "/app/worker.py"]

For full YouTube support, add a supported JS runtime such as Deno to the image and validate its version during the image build.

Do not use:

latest

everywhere in a serious production deployment.

Pin known-good versions or immutable image digests where practical.


Docker Compose Architecture

A small deployment might contain:

services:
  redis:
image: redis:7-alpine
restart: unless-stopped
  worker:
build: ./worker
restart: unless-stopped
depends_on:
  - redis
environment:
  REDIS_URL: redis://redis:6379/0
  STORAGE_PATH: /downloads
volumes:
  - ./downloads:/downloads

For multiple workers:

docker compose up -d --scale worker=5

For serious scale, Kubernetes, Nomad, ECS or another orchestrator is usually more appropriate than a single Docker Compose host.


Avoid running yt-dlp directly inside your public HTTP request handler.

Bad architecture:

POST /download
 |
 v
yt-dlp runs for 20 minutes
 |

HTTP request waits

Better:

POST /downloads
  |
  v

validate request | v create job | v return job_id | v queue | v worker

The API might immediately return:

{

"job_id": "job_abc123", "status": "queued"

}

The client then polls:

GET /downloads/job_abc123

or receives a webhook when processing completes.


Queue Architecture

A production queue can use:

Redis + BullMQ Redis + RQ Celery RabbitMQ AWS SQS Google Pub/Sub Kafka

You probably do not need Kafka just to run yt-dlp.

A Redis-based queue is sufficient for many installations.

Jobs should contain references, not secrets:

{

"job_id": "job_123", "source": "youtube", "url": "...", "format_policy": "1080p", "storage_key": "..."

}

Proxy credentials should be retrieved from a secrets system by the worker.


Storage Architecture

Downloading large video files directly onto the root filesystem is a common mistake.

A worker should have temporary scratch space:

/tmp/video-jobs/JOB_ID/

For example:

/tmp/video-jobs/abc123/video.webm /tmp/video-jobs/abc123/audio.m4a /tmp/video-jobs/abc123/final.mp4

After processing:

upload final object verify upload write metadata mark job complete delete temporary files


Object Storage

For distributed systems, object storage is usually preferable to a shared local disk.

Common choices include:

Amazon S3 Cloudflare R2 Backblaze B2 Google Cloud Storage MinIO

Example logical keys:

youtube/ CHANNEL_ID/ VIDEO_ID/ original.mp4 metadata.json thumbnail.jpg subtitles/ en.vtt

Another model is:

videos/{video_id}/source.mp4 videos/{video_id}/metadata.json videos/{video_id}/thumbnail.jpg

Keep the database separate from the object payload.

Database:

video_id platform source_url channel_id title duration status storage_key size_bytes checksum created_at completed_at

Object storage:

actual multi-megabyte/gigabyte media


Temporary Disk Capacity

Disk exhaustion is one of the easiest ways to kill a downloader fleet.

Suppose a worker processes four simultaneous 4 GB videos.

During processing you may temporarily hold:

video stream audio stream merged file

That means actual temporary usage can significantly exceed the final output size.

Workers should monitor:

df -h

and refuse new jobs below a configured free-space threshold.

For example:

MIN_FREE_SPACE=20GB


Checksums

After upload, calculate a checksum when integrity matters:

sha256sum final.mp4

Store it in your database.

This gives you:

integrity verification deduplication assistance corruption detection


Observability

A production downloader needs metrics.

Useful metrics include:

jobs_queued jobs_started jobs_completed jobs_failed download_duration_seconds download_bytes download_speed proxy_failures extractor_failures

ffmpeg_failures

storage_failures retry_count worker_disk_free_bytes

Track failure categories separately.

For example:

NETWORK_ERROR EXTRACTION_ERROR AUTH_ERROR VIDEO_UNAVAILABLE RATE_LIMITED PROXY_ERROR FFMPEG_ERROR STORAGE_ERROR INTERNAL_ERROR

Otherwise your monitoring dashboard will simply say:

12% failed

without telling you what actually broke.


Logging

Give every job a correlation ID:

job_id=abc123

Every worker log should contain it:

2026-08-22T12:00:01Z job_id=abc123 status=started 2026-08-22T12:00:02Z job_id=abc123 stage=metadata 2026-08-22T12:00:04Z job_id=abc123 stage=download 2026-08-22T12:02:31Z job_id=abc123 stage=upload 2026-08-22T12:02:38Z job_id=abc123 status=complete

Redact:

proxy passwords cookies Authorization headers signed storage URLs session tokens


Updating yt-dlp Safely

Never automatically update every production worker simultaneously.

Use:

new yt-dlp version | v build container | v unit tests | v integration tests | v canary worker | v production rollout

Test several representative cases:

normal video high-resolution video video with separate audio/video playlist Short live-related metadata subtitles thumbnail proxy path cookie-authenticated path if applicable

If extraction breaks after deployment:

rollback image

This is one of the major advantages of immutable Docker images.


Design and secure production infrastructure

Production Architecture

A more complete deployment can look like:

                         Internet
                            |
                            v
                     +-------------+
                     | API Gateway |
                     +------+------+
                            |
                            v
                     +-------------+
                     | API Service |
                     +------+------+
                            |
             +--------------+--------------+
             |                             |
             v                             v
        PostgreSQL                     Redis Queue
                                           |
                      +--------------------+--------------------+
                      |                    |                    |
                      v                    v                    v
                   Worker               Worker               Worker
                      |                    |                    |
                      +----------+---------+---------+----------+
                                 |
                                 v
                           Proxy Manager
                                 |
                                 v
                           Proxy Provider
                                 |
                                 v
                              YouTube

Workers:
    |
    +--> FFmpeg
    |
    +--> temporary SSD
    |
    +--> Object Storage

Monitoring:
    Prometheus / Grafana / logs / alerts

Security Model

Treat submitted URLs as untrusted input.

Do not construct shell commands like:

os.system("yt-dlp " + user_url)

That creates command-injection risk.

Instead use argument arrays:

subprocess.run([ "yt-dlp", "--no-playlist", user_url ])

Better yet, use yt-dlp's Python API when appropriate.

Also:

  • restrict supported URL schemes;
  • consider allowlisting supported domains if the service is YouTube-only;
  • never enable "file://" URLs for arbitrary users;
  • isolate workers;
  • run workers as non-root;
  • restrict filesystem access;
  • restrict cloud IAM permissions;
  • keep secrets outside images;
  • cap CPU, memory, disk and execution time.

AI Prompt: Build the Complete Architecture

The following prompt can be given to Cursor, Claude Code, Replit Agent, or another coding agent.

You are a senior infrastructure engineer.

Build a production-ready distributed video ingestion service around yt-dlp.

GOALS

Create an API that accepts authorized YouTube video URLs, creates asynchronous jobs, downloads and processes media using isolated workers, and stores resulting media in S3-compatible object storage.

TECH STACK

  • Ubuntu Linux
  • Docker
  • Docker Compose for local/single-server deployment
  • Python 3.12
  • FastAPI
  • PostgreSQL
  • Redis
  • yt-dlp
  • FFmpeg
  • Deno
  • S3-compatible object storage
  • Prometheus metrics
  • structured JSON logging

ARCHITECTURE

API:

  • POST /v1/downloads
  • GET /v1/downloads/{job_id}
  • DELETE /v1/downloads/{job_id}
  • GET /health
  • GET /ready
  • GET /metrics

Workers:

  • consume jobs from Redis
  • use yt-dlp
  • use FFmpeg
  • download to isolated temporary directories
  • upload completed objects to S3
  • delete temporary files after successful upload
  • retry transient failures with exponential backoff
  • send permanent failures to a dead-letter queue

SECURITY

  • never execute user input through shell=True
  • validate URLs
  • only allow http and https
  • optionally allowlist youtube.com and youtu.be
  • run containers as non-root
  • mount filesystems with minimal permissions
  • keep cookies and proxy credentials in secrets/environment configuration
  • redact secrets from logs
  • implement maximum execution time
  • implement maximum file size
  • implement concurrency limits

YT-DLP

Install current yt-dlp with: yt-dlp[default,curl-cffi]

Install:

  • FFmpeg
  • ffprobe
  • Deno

Create configurable format policies:

  • best
  • 2160p
  • 1440p
  • 1080p
  • 720p
  • audio

PROXY SUPPORT

Create a ProxyProvider interface.

Environment configuration:

PROXY_ENABLED PROXY_URL PROXY_USERNAME PROXY_PASSWORD

Never log proxy credentials.

Proxy selection must happen per job/session rather than randomly changing an endpoint in the middle of a media transfer.

STORAGE

Use an S3-compatible interface.

Object layout:

youtube/{channel_id}/{video_id}/source.{ext} youtube/{channel_id}/{video_id}/metadata.json youtube/{channel_id}/{video_id}/thumbnail.jpg youtube/{channel_id}/{video_id}/subtitles/{language}.{ext}

DATABASE

Create tables for: downloads download_attempts media_objects

Downloads should include:

id platform source_url source_video_id channel_id status format_policy storage_key file_size checksum error_code created_at started_at completed_at

Add a uniqueness strategy for platform + source_video_id where appropriate.

OBSERVABILITY

Expose Prometheus metrics:

jobs_queued jobs_running jobs_completed_total jobs_failed_total download_duration_seconds download_bytes_total proxy_errors_total extractor_errors_total

ffmpeg_errors_total

storage_errors_total

LOGGING

Use JSON logs.

Every log line must contain: job_id worker_id stage

Never log: cookies authorization headers proxy passwords signed URLs

DOCKER

Create: Dockerfile.api Dockerfile.worker docker-compose.yml

Use health checks.

Pin dependency versions.

Create persistent volumes for PostgreSQL and Redis where appropriate.

Use ephemeral storage for worker downloads.

TESTING

Create: unit tests integration tests worker health checks

yt-dlp simulation tests

FFmpeg tests storage tests proxy connectivity tests

DOCUMENTATION

Create a detailed README containing:

installation environment variables architecture development deployment upgrades rollback monitoring troubleshooting

Generate all source code, Docker files, database migrations, tests and documentation.

Do not leave TODO placeholders.

Do not put real credentials anywhere in the repository.


AI Prompt: Audit an Existing Deployment

Once the coding agent has created the infrastructure, give it a second independent task:

Act as a senior SRE and security engineer.

Audit this entire yt-dlp deployment.

Do not rewrite the project yet.

Inspect:

  1. Dockerfiles
  2. Docker Compose
  3. Python dependencies
  4. yt-dlp version
  5. FFmpeg installation
  6. Deno installation
  7. queue implementation
  8. retry strategy
  9. proxy implementation
  10. storage implementation
  11. temporary file cleanup
  12. database transactions
  13. command injection risks
  14. SSRF risks
  15. secret leakage
  16. log leakage
  17. cookie handling
  18. resource exhaustion
  19. disk exhaustion
  20. concurrency bugs
  21. race conditions
  22. duplicate downloads
  23. graceful shutdown
  24. worker crash recovery
  25. monitoring gaps
  26. dependency pinning
  27. backup requirements
  28. rollback procedure

Classify findings:

CRITICAL HIGH MEDIUM LOW

For each finding provide:

  • affected file
  • affected code
  • reason
  • exploitation/failure scenario
  • recommended fix

Then produce a prioritized remediation plan.

Do not claim an issue exists unless you can identify the relevant code path.

This second pass is valuable because coding agents are often better at finding mistakes when explicitly placed into review mode rather than being asked to review their own work while generating it.


Decide between self-hosting and VideoScale

The Operational Cost People Underestimate

The command:

yt-dlp URL

is free.

The infrastructure around it is not.

A self-hosted production system requires somebody to own:

servers containers queues databases storage FFmpeg JavaScript runtimes proxy infrastructure updates extractor regressions security patches monitoring retry logic disk cleanup autoscaling deployment incident response

This distinction becomes important when comparing self-hosted yt-dlp with a managed solution.


Self-Hosted yt-dlp vs VideoScale

For developers who only need occasional downloads, installing yt-dlp directly is often perfectly reasonable.

The equation changes when video downloading becomes part of a product.

With self-hosting, your application becomes responsible for an entire extraction infrastructure.

Conceptually:

Your Application
      |
      v
Queue
      |
      v
yt-dlp Workers
      |
      v
Proxy Infrastructure
      |
      v
FFmpeg
      |
      v
Storage

With a managed downloading layer such as VideoScale, the application-facing architecture can instead become:

Your Application
      |
      v
VideoScale
      |
      v
Video Result

The value is not that yt-dlp itself is difficult to execute.

The value is eliminating the operational machinery surrounding reliable downloads.


Why Use VideoScale Instead of Maintaining yt-dlp Yourself?

1. No yt-dlp Installation

You do not need to install or maintain:

Python yt-dlp yt-dlp-ejs Deno FFmpeg curl_cffi

on every application server.

Your application interacts with the managed service instead.


2. No Worker Fleet

Self-hosted systems eventually need:

worker containers queues autoscaling resource limits job timeouts dead-letter handling health checks rolling deployments

VideoScale moves that infrastructure outside your application stack.


3. No yt-dlp Update Pipeline

A production self-hosted environment should continuously track upstream yt-dlp changes.

The 2026 release history demonstrates why: JavaScript-runtime requirements changed, Python recommendations changed, security fixes landed, YouTube clients changed, and extractor behavior continues to evolve.

With a managed service, those compatibility updates become part of the service layer rather than your application deployment process.


4. Proxy Infrastructure Is Already Part of the Download Layer

At scale, outbound networking can become one of the most operationally demanding components.

VideoScale's download infrastructure uses rotating PrivateProxy residential proxy capacity as part of its networking layer, including session/rotation management and operational warming/health handling rather than requiring every customer to build their own proxy scheduler.

That means application developers do not need to maintain:

proxy pools credential distribution proxy health tracking session assignment rotation policies cooldowns regional routing logic

inside their own application.


5. Fewer Moving Parts

A self-hosted stack can contain:

API Redis PostgreSQL workers Docker yt-dlp FFmpeg Deno proxy provider object storage monitoring

Every component can fail independently.

Managed downloading reduces the number of components your own engineering team has to operate.


6. Easier Horizontal Scaling

Self-hosted scaling usually means deciding:

How many workers?

How many concurrent jobs per worker?

How much temporary disk?

How many proxy sessions?

How many fragment downloads?

When should workers autoscale?

What happens to jobs during shutdown?

A managed API makes the application side substantially simpler.


7. Cleaner Application Architecture

Instead of embedding video extraction infrastructure into your product, keep a clear boundary:

Product Logic
     |
     v
Video Download API
     |
     v
Storage / Processing

This separation can make the application easier to deploy, test and maintain.


When Should You Still Self-Host yt-dlp?

Self-hosting remains an excellent choice when:

  • download volume is low;
  • you need unusual yt-dlp options;
  • you want complete infrastructure control;
  • you have existing DevOps capacity;
  • the workload runs internally;
  • you need custom extractors or plugins;
  • you need unusual FFmpeg processing;
  • you already operate suitable storage and worker infrastructure.

For a developer downloading several videos occasionally, a single Linux box may be all that is necessary.

For a SaaS platform processing large numbers of user-submitted jobs, the operational equation is different.


For a small workload:

1 Ubuntu server 1 yt-dlp installation FFmpeg Deno local SSD optional object storage

Keep it simple.

Do not introduce Kubernetes because you downloaded ten videos yesterday.


At moderate volume:

API Redis PostgreSQL 3–10 Docker workers S3-compatible storage proxy integration where legitimately needed Prometheus centralized logs

This gives you useful horizontal scalability without excessive complexity.


At larger scale:

Load Balancer API replicas Managed PostgreSQL Managed Redis/SQS Container orchestrator Autoscaled worker pools Proxy management Object storage Metrics Distributed logs Alerts Dead-letter queues Canary yt-dlp deployments

At this point, you should explicitly compare the engineering cost of maintaining the stack against using VideoScale.


Validate production readiness

Production Checklist

Before calling your self-hosted downloader production-ready, verify all of the following.

Runtime

  • [ ] Python 3.11+
  • [ ] current yt-dlp build
  • [ ] FFmpeg installed
  • [ ] ffprobe installed
  • [ ] supported JavaScript runtime installed
  • [ ] yt-dlp-ejs support available
  • [ ] curl_cffi available if impersonation is needed

Infrastructure

  • [ ] queue configured
  • [ ] retries configured
  • [ ] dead-letter handling configured
  • [ ] concurrency capped
  • [ ] temporary disk monitored
  • [ ] temporary files cleaned
  • [ ] object storage configured
  • [ ] database deduplication configured

Networking

  • [ ] outbound connectivity tested
  • [ ] proxy connectivity tested if applicable
  • [ ] proxy credentials stored securely
  • [ ] proxy sessions assigned consistently
  • [ ] timeouts configured
  • [ ] retry storms prevented

Security

  • [ ] URLs validated
  • [ ] no shell=True with user input
  • [ ] file:// disabled
  • [ ] containers run as non-root
  • [ ] cookies stored as secrets
  • [ ] proxy passwords stored as secrets
  • [ ] logs redact credentials
  • [ ] workers have minimal IAM permissions
  • [ ] execution time capped
  • [ ] resource consumption capped

Operations

  • [ ] health endpoint
  • [ ] readiness endpoint
  • [ ] metrics
  • [ ] structured logs
  • [ ] alerts
  • [ ] yt-dlp canary deployment
  • [ ] rollback procedure
  • [ ] dependency update procedure

Troubleshooting

"Requested format is not available"

First inspect formats:

yt-dlp -F "VIDEO_URL"

Then modify the format selector.

Avoid assuming that every video has exactly the same codecs and resolutions.


FFmpeg Errors

Check:

which ffmpeg
which ffprobe

Then:

ffmpeg -version
ffprobe -version

Remember that yt-dlp needs the actual FFmpeg binaries, not a Python package named "ffmpeg".


Extraction Suddenly Stops Working

Check your version:

yt-dlp --version

Then inspect current upstream releases.

Because websites change independently of yt-dlp, an older stable release can stop extracting correctly even though nothing changed on your server.

Test a current build in staging or canary before rolling it across production.


Proxy Fails

First isolate the proxy from yt-dlp:

curl \

--proxy "$YTDLP_PROXY" \

  https://example.com/

Then:

yt-dlp \

--proxy "$YTDLP_PROXY"
--simulate
--verbose
"VIDEO_URL"

Determine whether the problem is:

DNS proxy authentication proxy connectivity TLS target extraction session consistency timeout

instead of repeatedly retrying the same command.


Worker Disk Is Full

Check:

df -h

Inspect temporary directories:

du -sh /tmp/*

Production workers should automatically clean failed jobs and enforce disk-space thresholds before accepting new jobs.


Final Architecture Recommendation

yt-dlp remains one of the most powerful building blocks available for developers working with online video in 2026.

For a single machine, the setup can be beautifully simple:

yt-dlp + FFmpeg + Deno

For production workloads, however, the architecture quickly evolves into:

API + Queue + Workers + yt-dlp + JavaScript runtime + FFmpeg + Proxy management + Temporary storage + Object storage + Database + Monitoring + Update pipeline

There is nothing inherently wrong with operating this infrastructure yourself. In fact, for teams requiring complete control, self-hosting can be the right decision.

But the comparison should be made against the whole system, not against the cost of running one command.

If your actual product requirement is simply:

«"Give my application a reliable way to retrieve a video without making video-extraction infrastructure part of my core stack,"»

then a managed service such as VideoScale can be substantially simpler.

Your engineering team can focus on what happens after the video is available—transcription, indexing, clipping, moderation, AI processing, analytics, archiving, or whatever your actual product does—rather than maintaining download workers, FFmpeg environments, proxy sessions, extraction compatibility, and yt-dlp updates.

And if you do decide to self-host, build the system around disposable workers, controlled concurrency, secure secrets, stable storage, observable queues, reproducible containers, and tested upgrades from day one.

That is the difference between running yt-dlp and operating yt-dlp at scale.

Sources and further reading