Files
unshackle/guide/proxies-and-vpn/index.html
T
2026-08-20 03:01:15 +00:00

3305 lines
100 KiB
HTML

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Movie, TV, and Music archival software. User guide and REST API reference">
<link rel="canonical" href="https://docs.unshackle.dev/guide/proxies-and-vpn/">
<link rel="prev" href="../drm-and-cdm/">
<link rel="next" href="../vaults/">
<link rel="icon" href="../../assets/favicon.svg">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.7">
<title>Proxies & VPN - unshackle</title>
<link rel="stylesheet" href="../../assets/stylesheets/main.ec1eaa64.min.css">
<link rel="stylesheet" href="../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<link rel="stylesheet" href="../../assets/_mkdocstrings.css">
<script>__md_scope=new URL("../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="black" data-md-color-accent="indigo">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#proxies-vpn" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../.." title="unshackle" class="md-header__button md-logo" aria-label="unshackle" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m800-274-80-80v-206H514l-80-80h166v-80q0-50-34.5-85T481-840q-50 0-84 34.5T363-720v9l-73-73q22-61 75-98.5T481-920q83 0 141 58.5T680-720v80h40q33 0 56.5 23.5T800-560v286Zm20 246-62-62q-11 5-20 7.5T720-80H240q-33 0-56.5-23.5T160-160v-400q0-25 14.5-46t37.5-30L28-820l56-56L876-84l-56 56ZM686-160 539-309q-11 11-25.5 17t-31.5 6q-33 0-56.5-23.5T402-366q0-17 6-31.5t17-25.5L286-560h-46v400h446ZM486-360Zm131-97Z"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
unshackle
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Proxies & VPN
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="(prefers-color-scheme)" data-md-color-scheme="default" data-md-color-primary="black" data-md-color-accent="indigo" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7a5 5 0 0 0-5 5 5 5 0 0 0 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1M8 13h8v-2H8zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4a5 5 0 0 0 5-5 5 5 0 0 0-5-5"/></svg>
</label>
<input class="md-option" data-md-color-media="(prefers-color-scheme: light)" data-md-color-scheme="default" data-md-color-primary="black" data-md-color-accent="indigo" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_2" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 7H7a5 5 0 0 0-5 5 5 5 0 0 0 5 5h10a5 5 0 0 0 5-5 5 5 0 0 0-5-5m0 8a3 3 0 0 1-3-3 3 3 0 0 1 3-3 3 3 0 0 1 3 3 3 3 0 0 1-3 3"/></svg>
</label>
<input class="md-option" data-md-color-media="(prefers-color-scheme: dark)" data-md-color-scheme="slate" data-md-color-primary="black" data-md-color-accent="indigo" aria-label="Switch to system preference" type="radio" name="__palette" id="__palette_2">
<label class="md-header__button md-icon" title="Switch to system preference" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 7H7a5 5 0 0 0-5 5 5 5 0 0 0 5 5h10a5 5 0 0 0 5-5 5 5 0 0 0-5-5M7 15a3 3 0 0 1-3-3 3 3 0 0 1 3-3 3 3 0 0 1 3 3 3 3 0 0 1-3 3"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/unshackle-dl/unshackle" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
unshackle-dl/unshackle
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../.." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item">
<a href="../../getting-started/installation/" class="md-tabs__link">
Getting Started
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="../downloading/" class="md-tabs__link">
User Guide
</a>
</li>
<li class="md-tabs__item">
<a href="../../reference/configuration/" class="md-tabs__link">
Configuration
</a>
</li>
<li class="md-tabs__item">
<a href="../../dev/creating-a-service/" class="md-tabs__link">
Developer
</a>
</li>
<li class="md-tabs__item">
<a href="../../reference/api/service/" class="md-tabs__link">
API Reference
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../.." title="unshackle" class="md-nav__button md-logo" aria-label="unshackle" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m800-274-80-80v-206H514l-80-80h166v-80q0-50-34.5-85T481-840q-50 0-84 34.5T363-720v9l-73-73q22-61 75-98.5T481-920q83 0 141 58.5T680-720v80h40q33 0 56.5 23.5T800-560v286Zm20 246-62-62q-11 5-20 7.5T720-80H240q-33 0-56.5-23.5T160-160v-400q0-25 14.5-46t37.5-30L28-820l56-56L876-84l-56 56ZM686-160 539-309q-11 11-25.5 17t-31.5 6q-33 0-56.5-23.5T402-366q0-17 6-31.5t17-25.5L286-560h-46v400h446ZM486-360Zm131-97Z"/></svg>
</a>
unshackle
</label>
<div class="md-nav__source">
<a href="https://github.com/unshackle-dl/unshackle" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
unshackle-dl/unshackle
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" >
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
<span class="md-ellipsis">
Getting Started
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
Getting Started
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../getting-started/installation/" class="md-nav__link">
<span class="md-ellipsis">
Installation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../getting-started/quickstart/" class="md-nav__link">
<span class="md-ellipsis">
Quick Start
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../getting-started/configuration-file/" class="md-nav__link">
<span class="md-ellipsis">
The Configuration File
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" checked>
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="">
<span class="md-ellipsis">
User Guide
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_3">
<span class="md-nav__icon md-icon"></span>
User Guide
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../downloading/" class="md-nav__link">
<span class="md-ellipsis">
Downloading
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../cli-reference/" class="md-nav__link">
<span class="md-ellipsis">
CLI Reference
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../drm-and-cdm/" class="md-nav__link">
<span class="md-ellipsis">
DRM & CDM Setup
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Proxies & VPN
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Proxies & VPN
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#the-proxy-flag" class="md-nav__link">
<span class="md-ellipsis">
The --proxy flag
</span>
</a>
<nav class="md-nav" aria-label="The --proxy flag">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#-no-proxy-download" class="md-nav__link">
<span class="md-ellipsis">
--no-proxy-download
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#how-proxy-resolution-works" class="md-nav__link">
<span class="md-ellipsis">
How proxy resolution works
</span>
</a>
<nav class="md-nav" aria-label="How proxy resolution works">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#provider-load-order" class="md-nav__link">
<span class="md-ellipsis">
Provider load order
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#connection-feedback" class="md-nav__link">
<span class="md-ellipsis">
Connection feedback
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#basic-static-proxies" class="md-nav__link">
<span class="md-ellipsis">
Basic (static proxies)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#gluetun" class="md-nav__link">
<span class="md-ellipsis">
Gluetun
</span>
</a>
<nav class="md-nav" aria-label="Gluetun">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#query-format" class="md-nav__link">
<span class="md-ellipsis">
Query format
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#configuration" class="md-nav__link">
<span class="md-ellipsis">
Configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#region-selection" class="md-nav__link">
<span class="md-ellipsis">
Region selection
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#container-lifecycle" class="md-nav__link">
<span class="md-ellipsis">
Container lifecycle
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#expressvpn" class="md-nav__link">
<span class="md-ellipsis">
ExpressVPN
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nordvpn" class="md-nav__link">
<span class="md-ellipsis">
NordVPN
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#proton-vpn" class="md-nav__link">
<span class="md-ellipsis">
Proton VPN
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#hola" class="md-nav__link">
<span class="md-ellipsis">
Hola
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#surfshark" class="md-nav__link">
<span class="md-ellipsis">
Surfshark
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#windscribe" class="md-nav__link">
<span class="md-ellipsis">
Windscribe
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#developer-notes" class="md-nav__link">
<span class="md-ellipsis">
Developer notes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#see-also" class="md-nav__link">
<span class="md-ellipsis">
See also
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../vaults/" class="md-nav__link">
<span class="md-ellipsis">
Key Vaults
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../output-and-naming/" class="md-nav__link">
<span class="md-ellipsis">
Output & Naming
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../subtitles/" class="md-nav__link">
<span class="md-ellipsis">
Subtitles
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../troubleshooting/" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_4" >
<div class="md-nav__link md-nav__container">
<a href="../../reference/configuration/" class="md-nav__link ">
<span class="md-ellipsis">
Configuration
</span>
</a>
<label class="md-nav__link " for="__nav_4" id="__nav_4_label" tabindex="0">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_4_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_4">
<span class="md-nav__icon md-icon"></span>
Configuration
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../reference/configuration/directories/" class="md-nav__link">
<span class="md-ellipsis">
Directories
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/configuration/services/" class="md-nav__link">
<span class="md-ellipsis">
Services & Authentication
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/configuration/download/" class="md-nav__link">
<span class="md-ellipsis">
Download & Processing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/configuration/output/" class="md-nav__link">
<span class="md-ellipsis">
Output & Naming
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/configuration/drm/" class="md-nav__link">
<span class="md-ellipsis">
DRM & CDM
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/configuration/network/" class="md-nav__link">
<span class="md-ellipsis">
Network & Proxy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/configuration/vaults/" class="md-nav__link">
<span class="md-ellipsis">
Key Vaults
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/configuration/post-scripts/" class="md-nav__link">
<span class="md-ellipsis">
Post-Download Scripts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/configuration/misc/" class="md-nav__link">
<span class="md-ellipsis">
Other Settings
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/configuration/example/" class="md-nav__link">
<span class="md-ellipsis">
Annotated Example
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5" >
<label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="0">
<span class="md-ellipsis">
Developer
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_5_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5">
<span class="md-nav__icon md-icon"></span>
Developer
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../dev/creating-a-service/" class="md-nav__link">
<span class="md-ellipsis">
Creating a Service
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../dev/service-architecture/" class="md-nav__link">
<span class="md-ellipsis">
Service Architecture
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5_3" >
<div class="md-nav__link md-nav__container">
<a href="../../dev/rest-api/" class="md-nav__link ">
<span class="md-ellipsis">
REST API
</span>
</a>
<label class="md-nav__link " for="__nav_5_3" id="__nav_5_3_label" tabindex="0">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_3_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5_3">
<span class="md-nav__icon md-icon"></span>
REST API
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../dev/rest-api/quickstart/" class="md-nav__link">
<span class="md-ellipsis">
Quick Start
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../dev/rest-api/authentication/" class="md-nav__link">
<span class="md-ellipsis">
Authentication
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../dev/rest-api/endpoints/" class="md-nav__link">
<span class="md-ellipsis">
Endpoints
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../dev/rest-api/job-lifecycle/" class="md-nav__link">
<span class="md-ellipsis">
Job Lifecycle
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../dev/rest-api/remote-sessions/" class="md-nav__link">
<span class="md-ellipsis">
Remote Sessions
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../dev/rest-api/errors/" class="md-nav__link">
<span class="md-ellipsis">
Error Responses
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_6" >
<label class="md-nav__link" for="__nav_6" id="__nav_6_label" tabindex="0">
<span class="md-ellipsis">
API Reference
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_6_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_6">
<span class="md-nav__icon md-icon"></span>
API Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../reference/api/service/" class="md-nav__link">
<span class="md-ellipsis">
Service
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/api/config/" class="md-nav__link">
<span class="md-ellipsis">
Config
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/api/tracks/" class="md-nav__link">
<span class="md-ellipsis">
Tracks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/api/titles/" class="md-nav__link">
<span class="md-ellipsis">
Titles
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/api/drm/" class="md-nav__link">
<span class="md-ellipsis">
DRM
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#the-proxy-flag" class="md-nav__link">
<span class="md-ellipsis">
The --proxy flag
</span>
</a>
<nav class="md-nav" aria-label="The --proxy flag">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#-no-proxy-download" class="md-nav__link">
<span class="md-ellipsis">
--no-proxy-download
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#how-proxy-resolution-works" class="md-nav__link">
<span class="md-ellipsis">
How proxy resolution works
</span>
</a>
<nav class="md-nav" aria-label="How proxy resolution works">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#provider-load-order" class="md-nav__link">
<span class="md-ellipsis">
Provider load order
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#connection-feedback" class="md-nav__link">
<span class="md-ellipsis">
Connection feedback
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#basic-static-proxies" class="md-nav__link">
<span class="md-ellipsis">
Basic (static proxies)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#gluetun" class="md-nav__link">
<span class="md-ellipsis">
Gluetun
</span>
</a>
<nav class="md-nav" aria-label="Gluetun">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#query-format" class="md-nav__link">
<span class="md-ellipsis">
Query format
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#configuration" class="md-nav__link">
<span class="md-ellipsis">
Configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#region-selection" class="md-nav__link">
<span class="md-ellipsis">
Region selection
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#container-lifecycle" class="md-nav__link">
<span class="md-ellipsis">
Container lifecycle
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#expressvpn" class="md-nav__link">
<span class="md-ellipsis">
ExpressVPN
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#nordvpn" class="md-nav__link">
<span class="md-ellipsis">
NordVPN
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#proton-vpn" class="md-nav__link">
<span class="md-ellipsis">
Proton VPN
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#hola" class="md-nav__link">
<span class="md-ellipsis">
Hola
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#surfshark" class="md-nav__link">
<span class="md-ellipsis">
Surfshark
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#windscribe" class="md-nav__link">
<span class="md-ellipsis">
Windscribe
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#developer-notes" class="md-nav__link">
<span class="md-ellipsis">
Developer notes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#see-also" class="md-nav__link">
<span class="md-ellipsis">
See also
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="proxies-vpn">Proxies &amp; VPN<a class="headerlink" href="#proxies-vpn" title="Permanent link">&para;</a></h1>
<p>Most streaming services are geo-restricted, region-priced, or refuse to hand
over a manifest from the "wrong" country. unshackle routes its traffic through a proxy
so the service sees the location you choose instead of your own. You can point it at a
single proxy URL, or configure one of several <strong>proxy providers</strong> (commercial VPNs and
self-hosted tunnels), then ask for a country by its two-letter code and let
unshackle pick a working server for you.</p>
<p>The <code>--proxy</code> flag controls proxying, and the proxy providers unshackle supports change
a query to a real proxy URL: <a href="#basic-static-proxies">basic static proxies</a>,
<a href="#gluetun">Gluetun</a> (documented in the most detail, since it is the most flexible),
<a href="#expressvpn">ExpressVPN</a>, <a href="#nordvpn">NordVPN</a>, <a href="#proton-vpn">Proton VPN</a>,
<a href="#hola">Hola</a>, <a href="#surfshark">Surfshark</a>, and <a href="#windscribe">Windscribe</a>.</p>
<div class="admonition note">
<p class="admonition-title">Where proxies are configured</p>
<p>All proxy provider configuration lives under the top-level <code>proxy_providers:</code>
config key in your <code>unshackle.yaml</code>. Each proxy provider has its own sub-key, and
the config keys inside it map directly onto that proxy provider's settings. See the
<a href="../../getting-started/configuration-file/">Configuration file</a> page for where the
file lives and how unshackle loads it.</p>
</div>
<h2 id="the-proxy-flag">The <code>--proxy</code> flag<a class="headerlink" href="#the-proxy-flag" title="Permanent link">&para;</a></h2>
<p>Three options on the <code>dl</code> command control proxying at download time:</p>
<table>
<thead>
<tr>
<th>Option</th>
<th>Effect</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--proxy</code></td>
<td>The proxy to use. Either an explicit URI, or a query that unshackle resolves against your configured providers.</td>
</tr>
<tr>
<td><code>--no-proxy</code></td>
<td>Force <strong>all</strong> proxy use off. No providers are initialised and no proxy query is resolved.</td>
</tr>
<tr>
<td><code>--no-proxy-download</code></td>
<td>Use the proxy for the manifest, licence, and authentication, but bypass it for the <strong>downloads</strong> themselves.</td>
</tr>
</tbody>
</table>
<div class="language-shell highlight"><span class="filename">Explicit proxy URI</span><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a>unshackle<span class="w"> </span>dl<span class="w"> </span>--proxy<span class="w"> </span>http://user:pass@1.2.3.4:8080<span class="w"> </span>EXAMPLE<span class="w"> </span><span class="m">81234567</span>
</span></code></pre></div>
<div class="language-shell highlight"><span class="filename">Resolve a US server from your configured providers</span><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a>unshackle<span class="w"> </span>dl<span class="w"> </span>--proxy<span class="w"> </span>us<span class="w"> </span>EXAMPLE<span class="w"> </span><span class="m">81234567</span>
</span></code></pre></div>
<div class="language-shell highlight"><span class="filename">Target a specific provider</span><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a>unshackle<span class="w"> </span>dl<span class="w"> </span>--proxy<span class="w"> </span>nordvpn:us<span class="w"> </span>EXAMPLE<span class="w"> </span><span class="m">81234567</span>
</span></code></pre></div>
<div class="admonition tip">
<p class="admonition-title">Set a default proxy in config</p>
<p>Like every <code>dl</code> flag, you can give <code>--proxy</code> a default in your configuration, so you
do not have to pass it on every command. See
<a href="../downloading/">Downloading</a> for how <code>dl</code> options and config defaults interact.</p>
</div>
<h3 id="-no-proxy-download"><code>--no-proxy-download</code><a class="headerlink" href="#-no-proxy-download" title="Permanent link">&para;</a></h3>
<p>Segment downloads are usually the largest share of traffic, and a residential-grade VPN
proxy is frequently the slowest part of the chain. <code>--no-proxy-download</code> lets you keep the
proxy where it matters (passing the geo-check for the manifest and the licence) while
pulling the files themselves over your normal connection at full speed.</p>
<div class="language-shell highlight"><span class="filename">Proxy the manifest and licence, download segments direct</span><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a>unshackle<span class="w"> </span>dl<span class="w"> </span>--proxy<span class="w"> </span>gb<span class="w"> </span>--no-proxy-download<span class="w"> </span>EXAMPLE<span class="w"> </span>10a1234
</span></code></pre></div>
<div class="admonition warning">
<p class="admonition-title">When this is unsafe</p>
<p>Some services tie segment delivery (CDN tokens, per-segment geo-checks) to the same
region as the manifest. On those services, bypassing the proxy for downloads will
cause segment fetches to fail or return the wrong region. If downloads break with
<code>--no-proxy-download</code>, remove it.</p>
</div>
<h2 id="how-proxy-resolution-works">How proxy resolution works<a class="headerlink" href="#how-proxy-resolution-works" title="Permanent link">&para;</a></h2>
<p>unshackle interprets the value you pass to <code>--proxy</code> in one of three ways. It tries
them in this order.</p>
<pre class="mermaid"><code>flowchart TD
A["--proxy value"] --&gt; B{"Looks like a URI?&lt;br/&gt;http:// https:// socks"}
B -- yes --&gt; C["Use it verbatim"]
B -- no --&gt; D{"Has a provider prefix?&lt;br/&gt;provider:query"}
D -- yes --&gt; E["Ask that one provider&lt;br/&gt;for the query"]
D -- no --&gt; F["Ask every provider in order,&lt;br/&gt;first match wins"]</code></pre>
<ol>
<li><strong>An explicit URI.</strong> unshackle uses anything shaped like <code>http://...</code>,
<code>https://...</code>, or a <code>socks...</code> URI exactly as you give it. unshackle logs
<code>Using explicit Proxy: ...</code> and does no lookup.</li>
<li><strong>A provider-prefixed query</strong>: <code>provider:query</code>, for example <code>nordvpn:us</code> or
<code>gluetun:windscribe:us</code>. unshackle finds the proxy provider whose name matches
the prefix (case-insensitive). It then asks <em>only</em> that proxy provider to find a
proxy for the remainder. If unshackle loads no such proxy provider, or if that
proxy provider returns no proxy, the download errors.</li>
<li><strong>A bare query</strong>: a region like <code>us</code>, <code>gb</code>, <code>us:seattle</code>, or <code>us1234</code>. unshackle
asks each loaded proxy provider <strong>in order</strong> and uses the first proxy any of them
returns.</li>
</ol>
<p>unshackle compares the query against a region grammar: a two-letter country code,
optionally followed by a server number and/or a <code>:city</code> or <code>-city</code> part. It then
lowercases the query and hands it to the proxy provider. Its section below gives the
exact forms each proxy provider accepts.</p>
<h3 id="provider-load-order">Provider load order<a class="headerlink" href="#provider-load-order" title="Permanent link">&para;</a></h3>
<p>When you do a download without <code>--no-proxy</code>, unshackle instantiates every configured
proxy provider once, in this fixed order. It uses that same order for bare-query
resolution:</p>
<p><strong>Basic → ExpressVPN → NordVPN → Proton VPN → Surfshark → Windscribe → Gluetun → Hola</strong></p>
<p>So if you have both Basic and NordVPN configured and use <code>--proxy us</code>, a <code>us</code> entry in
your Basic config wins, because unshackle tries Basic first. To skip ahead to a specific
proxy provider, prefix the query (<code>--proxy nordvpn:us</code>).</p>
<div class="admonition note">
<p class="admonition-title">Auto-loading providers</p>
<p>Most providers only load when you configure them under <code>proxy_providers:</code>. Three are
special: <strong>ExpressVPN</strong> and <strong>Proton VPN</strong> also load automatically once their cache
file exists on disk (even with no YAML), and <strong>Hola</strong> loads automatically
whenever the <code>hola-proxy</code> binary is found on your <code>PATH</code>.</p>
</div>
<h3 id="connection-feedback">Connection feedback<a class="headerlink" href="#connection-feedback" title="Permanent link">&para;</a></h3>
<p>After a proxy provider resolves a query, unshackle prints a line so you can see
where you came out:</p>
<ul>
<li><strong>Gluetun</strong> reports the verified exit IP, country, and city of the container.</li>
<li><strong>ExpressVPN</strong> and <strong>Proton VPN</strong> print a location summary such as
<code>(USA - New York, #3 of 5): .214</code>.</li>
<li>Other proxy providers log the resolved proxy URL.</li>
</ul>
<h2 id="basic-static-proxies">Basic (static proxies)<a class="headerlink" href="#basic-static-proxies" title="Permanent link">&para;</a></h2>
<p>The <code>Basic</code> proxy provider is pure static configuration, with no accounts and no network
calls. You record proxy URLs under each country code, and unshackle serves them back
when you query that country. This is the right choice when you already have proxy endpoints (from a proxy
seller, your own servers, or a corporate gateway).</p>
<p>Under each two-letter country code you can put either a single proxy string or a list of
them:</p>
<div class="language-yaml highlight"><span class="filename">unshackle.yaml</span><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1" href="#__codelineno-4-1"></a><span class="nt">proxy_providers</span><span class="p">:</span>
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2" href="#__codelineno-4-2"></a><span class="w"> </span><span class="nt">basic</span><span class="p">:</span>
</span><span id="__span-4-3"><a id="__codelineno-4-3" name="__codelineno-4-3" href="#__codelineno-4-3"></a><span class="w"> </span><span class="nt">us</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">http://user:pass@1.2.3.4:8080</span>
</span><span id="__span-4-4"><a id="__codelineno-4-4" name="__codelineno-4-4" href="#__codelineno-4-4"></a><span class="w"> </span><span class="nt">de</span><span class="p">:</span>
</span><span id="__span-4-5"><a id="__codelineno-4-5" name="__codelineno-4-5" href="#__codelineno-4-5"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">http://a.example:8080</span>
</span><span id="__span-4-6"><a id="__codelineno-4-6" name="__codelineno-4-6" href="#__codelineno-4-6"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">socks5://b.example:1080</span>
</span></code></pre></div>
<p><strong>Query forms:</strong></p>
<table>
<thead>
<tr>
<th>Query</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>us</code></td>
<td>A proxy for the US. If several are listed, one is chosen at random.</td>
</tr>
<tr>
<td><code>us2</code></td>
<td>The <strong>2<sup>nd</sup></strong> entry in the US list (1-based).</td>
</tr>
</tbody>
</table>
<ul>
<li>If a country has a single proxy string, that proxy is always returned.</li>
<li>If it has a list and you give a plain country code, unshackle picks one at random.
Append an index (<code>de1</code>, <code>de2</code>) to pin a specific entry.</li>
<li>unshackle normalises each URL (a missing scheme defaults to <code>http</code>) and validates it,
so it rejects a malformed URI up front and does not fail mid-download.</li>
</ul>
<div class="language-shell highlight"><pre><span></span><code><span id="__span-5-1"><a id="__codelineno-5-1" name="__codelineno-5-1" href="#__codelineno-5-1"></a>unshackle<span class="w"> </span>dl<span class="w"> </span>--proxy<span class="w"> </span>de2<span class="w"> </span>EXAMPLE<span class="w"> </span><span class="m">81234567</span>
</span></code></pre></div>
<h2 id="gluetun">Gluetun<a class="headerlink" href="#gluetun" title="Permanent link">&para;</a></h2>
<p>Gluetun is unshackle's most flexible proxy backend, and the one to reach for if you want
one configuration to cover many VPN providers. Instead of returning a remote proxy URL,
Gluetun <strong>launches a local Docker container</strong> running the
<a href="https://github.com/qdm12/gluetun"><code>qmcgaw/gluetun</code></a> image. That container establishes a
WireGuard or OpenVPN tunnel to your chosen VPN provider and exposes a plain HTTP proxy on
<code>localhost</code>, which unshackle then routes traffic through. When the download finishes, the
container is torn down automatically.</p>
<p>This means you get access to the <strong>50+ VPN providers</strong> that Gluetun supports, using your own
VPN subscription, through a single uniform interface, with no per-provider integration
in unshackle itself.</p>
<div class="admonition warning">
<p class="admonition-title">Docker is required</p>
<p>Gluetun needs a working <a href="https://docs.docker.com/get-docker/">Docker</a> installation on
your <code>PATH</code>. If Docker is not found, the proxy provider fails to load with an error linking
to the install docs. The container also needs the <code>NET_ADMIN</code> capability and access
to <code>/dev/net/tun</code> to bring up the tunnel. unshackle requests both automatically.</p>
</div>
<h3 id="query-format">Query format<a class="headerlink" href="#query-format" title="Permanent link">&para;</a></h3>
<p>Gluetun uses a nested selector: the proxy provider prefix picks Gluetun, and the rest names the
<strong>VPN provider</strong> and the <strong>region</strong>.</p>
<div class="language-text highlight"><pre><span></span><code><span id="__span-6-1"><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a>gluetun:&lt;vpn-provider&gt;:&lt;region&gt;
</span></code></pre></div>
<div class="language-shell highlight"><span class="filename">A Windscribe US exit via Gluetun</span><pre><span></span><code><span id="__span-7-1"><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a>unshackle<span class="w"> </span>dl<span class="w"> </span>--proxy<span class="w"> </span>gluetun:windscribe:us<span class="w"> </span>EXAMPLE<span class="w"> </span><span class="m">81234567</span>
</span></code></pre></div>
<div class="language-shell highlight"><span class="filename">A NordVPN Germany exit via Gluetun</span><pre><span></span><code><span id="__span-8-1"><a id="__codelineno-8-1" name="__codelineno-8-1" href="#__codelineno-8-1"></a>unshackle<span class="w"> </span>dl<span class="w"> </span>--proxy<span class="w"> </span>gluetun:nordvpn:de<span class="w"> </span>EXAMPLE<span class="w"> </span><span class="m">81234567</span>
</span></code></pre></div>
<p>After unshackle strips the leading <code>gluetun:</code>, the proxy provider receives exactly
<code>provider:region</code>. It must be two colon-separated parts, and anything else is an error.
The <code>&lt;vpn-provider&gt;</code> must be one you have configured under <code>gluetun.providers</code>, and
unshackle normalises friendly names to Gluetun's identifiers (for example <code>pia</code> and
<code>privateinternetaccess</code> both map to <code>private internet access</code>).</p>
<h3 id="configuration">Configuration<a class="headerlink" href="#configuration" title="Permanent link">&para;</a></h3>
<p>Each VPN provider you want to use gets an entry under <code>providers:</code>, carrying its VPN type,
credentials, and how your region aliases map onto Gluetun's server selectors.</p>
<div class="language-yaml highlight"><span class="filename">unshackle.yaml</span><pre><span></span><code><span id="__span-9-1"><a id="__codelineno-9-1" name="__codelineno-9-1" href="#__codelineno-9-1"></a><span class="nt">proxy_providers</span><span class="p">:</span>
</span><span id="__span-9-2"><a id="__codelineno-9-2" name="__codelineno-9-2" href="#__codelineno-9-2"></a><span class="w"> </span><span class="nt">gluetun</span><span class="p">:</span>
</span><span id="__span-9-3"><a id="__codelineno-9-3" name="__codelineno-9-3" href="#__codelineno-9-3"></a><span class="w"> </span><span class="nt">providers</span><span class="p">:</span>
</span><span id="__span-9-4"><a id="__codelineno-9-4" name="__codelineno-9-4" href="#__codelineno-9-4"></a><span class="w"> </span><span class="nt">windscribe</span><span class="p">:</span>
</span><span id="__span-9-5"><a id="__codelineno-9-5" name="__codelineno-9-5" href="#__codelineno-9-5"></a><span class="w"> </span><span class="nt">vpn_type</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">wireguard</span>
</span><span id="__span-9-6"><a id="__codelineno-9-6" name="__codelineno-9-6" href="#__codelineno-9-6"></a><span class="w"> </span><span class="nt">credentials</span><span class="p">:</span>
</span><span id="__span-9-7"><a id="__codelineno-9-7" name="__codelineno-9-7" href="#__codelineno-9-7"></a><span class="w"> </span><span class="nt">private_key</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">YOUR_WIREGUARD_PRIVATE_KEY</span>
</span><span id="__span-9-8"><a id="__codelineno-9-8" name="__codelineno-9-8" href="#__codelineno-9-8"></a><span class="w"> </span><span class="nt">addresses</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">YOUR_WIREGUARD_ADDRESS</span>
</span><span id="__span-9-9"><a id="__codelineno-9-9" name="__codelineno-9-9" href="#__codelineno-9-9"></a><span class="w"> </span><span class="nt">preshared_key</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">YOUR_PRESHARED_KEY</span>
</span><span id="__span-9-10"><a id="__codelineno-9-10" name="__codelineno-9-10" href="#__codelineno-9-10"></a><span class="w"> </span><span class="nt">server_countries</span><span class="p">:</span>
</span><span id="__span-9-11"><a id="__codelineno-9-11" name="__codelineno-9-11" href="#__codelineno-9-11"></a><span class="w"> </span><span class="nt">us</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">US</span>
</span><span id="__span-9-12"><a id="__codelineno-9-12" name="__codelineno-9-12" href="#__codelineno-9-12"></a><span class="w"> </span><span class="nt">uk</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">GB</span>
</span><span id="__span-9-13"><a id="__codelineno-9-13" name="__codelineno-9-13" href="#__codelineno-9-13"></a><span class="w"> </span><span class="nt">server_cities</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">{}</span>
</span><span id="__span-9-14"><a id="__codelineno-9-14" name="__codelineno-9-14" href="#__codelineno-9-14"></a><span class="w"> </span><span class="nt">server_hostnames</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">{}</span>
</span><span id="__span-9-15"><a id="__codelineno-9-15" name="__codelineno-9-15" href="#__codelineno-9-15"></a><span class="w"> </span><span class="nt">extra_env</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">{}</span>
</span><span id="__span-9-16"><a id="__codelineno-9-16" name="__codelineno-9-16" href="#__codelineno-9-16"></a><span class="w"> </span><span class="nt">nordvpn</span><span class="p">:</span>
</span><span id="__span-9-17"><a id="__codelineno-9-17" name="__codelineno-9-17" href="#__codelineno-9-17"></a><span class="w"> </span><span class="nt">vpn_type</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">wireguard</span>
</span><span id="__span-9-18"><a id="__codelineno-9-18" name="__codelineno-9-18" href="#__codelineno-9-18"></a><span class="w"> </span><span class="nt">credentials</span><span class="p">:</span>
</span><span id="__span-9-19"><a id="__codelineno-9-19" name="__codelineno-9-19" href="#__codelineno-9-19"></a><span class="w"> </span><span class="nt">private_key</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">YOUR_WIREGUARD_PRIVATE_KEY</span>
</span><span id="__span-9-20"><a id="__codelineno-9-20" name="__codelineno-9-20" href="#__codelineno-9-20"></a><span class="w"> </span><span class="nt">server_countries</span><span class="p">:</span>
</span><span id="__span-9-21"><a id="__codelineno-9-21" name="__codelineno-9-21" href="#__codelineno-9-21"></a><span class="w"> </span><span class="nt">us</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">US</span>
</span><span id="__span-9-22"><a id="__codelineno-9-22" name="__codelineno-9-22" href="#__codelineno-9-22"></a><span class="w"> </span><span class="nt">de</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">DE</span>
</span><span id="__span-9-23"><a id="__codelineno-9-23" name="__codelineno-9-23" href="#__codelineno-9-23"></a>
</span><span id="__span-9-24"><a id="__codelineno-9-24" name="__codelineno-9-24" href="#__codelineno-9-24"></a><span class="w"> </span><span class="c1"># Global settings (all optional)</span>
</span><span id="__span-9-25"><a id="__codelineno-9-25" name="__codelineno-9-25" href="#__codelineno-9-25"></a><span class="w"> </span><span class="nt">base_port</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">8888</span>
</span><span id="__span-9-26"><a id="__codelineno-9-26" name="__codelineno-9-26" href="#__codelineno-9-26"></a><span class="w"> </span><span class="nt">auto_cleanup</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span>
</span><span id="__span-9-27"><a id="__codelineno-9-27" name="__codelineno-9-27" href="#__codelineno-9-27"></a><span class="w"> </span><span class="nt">container_prefix</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">unshackle-gluetun</span>
</span><span id="__span-9-28"><a id="__codelineno-9-28" name="__codelineno-9-28" href="#__codelineno-9-28"></a><span class="w"> </span><span class="nt">auth_user</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">null</span>
</span><span id="__span-9-29"><a id="__codelineno-9-29" name="__codelineno-9-29" href="#__codelineno-9-29"></a><span class="w"> </span><span class="nt">auth_password</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">null</span>
</span><span id="__span-9-30"><a id="__codelineno-9-30" name="__codelineno-9-30" href="#__codelineno-9-30"></a><span class="w"> </span><span class="nt">verify_ip</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span>
</span></code></pre></div>
<p><strong>Per-provider keys:</strong></p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>vpn_type</code></td>
<td><code>wireguard</code> (default) or <code>openvpn</code>.</td>
</tr>
<tr>
<td><code>credentials</code></td>
<td>The secrets for the tunnel; see the credential rules below.</td>
</tr>
<tr>
<td><code>server_countries</code></td>
<td>Maps your region alias (e.g. <code>us</code>) to Gluetun's country value (e.g. <code>US</code>).</td>
</tr>
<tr>
<td><code>server_cities</code></td>
<td>Optional. Maps an alias to a Gluetun city.</td>
</tr>
<tr>
<td><code>server_hostnames</code></td>
<td>Optional. Maps an alias directly to a specific server hostname.</td>
</tr>
<tr>
<td><code>extra_env</code></td>
<td>Optional. Raw environment variables merged into the container last, for any Gluetun setting not covered above.</td>
</tr>
</tbody>
</table>
<p><strong>Credential rules by VPN type:</strong></p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:2"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">WireGuard</label><label for="__tabbed_1_2">OpenVPN</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
<ul>
<li><code>private_key</code> is <strong>always</strong> required.</li>
<li><code>surfshark</code>, <code>mullvad</code>, and <code>ivpn</code> also require <code>addresses</code>.</li>
<li><code>windscribe</code> additionally requires <strong>both</strong> <code>addresses</code> and <code>preshared_key</code>
(<code>preshared_key</code> may be an empty string, but the config key must be present).</li>
<li>NordVPN and Proton VPN need only <code>private_key</code>.</li>
</ul>
</div>
<div class="tabbed-block">
<ul>
<li>Requires <code>username</code> and <code>password</code>.</li>
</ul>
</div>
</div>
</div>
<div class="admonition tip">
<p class="admonition-title">Which <code>vpn_type</code> should I pick?</p>
<p>WireGuard is the default because it <strong>can be faster</strong>, but it needs the fiddlier
credential setup above (private keys, and for some providers <code>addresses</code> and a
<code>preshared_key</code>). OpenVPN is the <strong>simplest</strong> to configure (only a username and
password) at the cost of some speed. If you start out, or if you have credential
trouble, OpenVPN is the path of least resistance. Switch to WireGuard when you want
the extra throughput.</p>
</div>
<p><strong>Global keys:</strong></p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Default</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>base_port</code></td>
<td><code>8888</code></td>
<td>The first local port to expose proxies on. Additional containers take the next free ports.</td>
</tr>
<tr>
<td><code>auto_cleanup</code></td>
<td><code>true</code></td>
<td>Remove containers on exit. If <code>false</code>, containers are only stopped, not deleted.</td>
</tr>
<tr>
<td><code>container_prefix</code></td>
<td><code>unshackle-gluetun</code></td>
<td>Prefix for Docker container names.</td>
</tr>
<tr>
<td><code>auth_user</code> / <code>auth_password</code></td>
<td><code>null</code></td>
<td>Optional HTTP-proxy credentials. If set, the local proxy requires auth and the credentials are embedded in the proxy URL.</td>
</tr>
<tr>
<td><code>verify_ip</code></td>
<td><code>true</code></td>
<td>After connecting, verify the container's real exit IP and region.</td>
</tr>
</tbody>
</table>
<h3 id="region-selection">Region selection<a class="headerlink" href="#region-selection" title="Permanent link">&para;</a></h3>
<p>For a given query region, Gluetun resolves the server like this:</p>
<ol>
<li>An explicit entry in <code>server_hostnames</code>, <code>server_cities</code>, or <code>server_countries</code> for
that alias always wins.</li>
<li>unshackle changes a <code>us1239</code>-style query (country code + number) with no explicit
mapping into a provider-specific server hostname (for example <code>us1239.nordvpn.com</code>
for NordVPN, <code>us-1239.prod.surfshark.com</code> for Surfshark).</li>
<li>unshackle expands a bare two-letter code to the full country name Gluetun expects.</li>
</ol>
<p>A few providers (Windscribe, VyprVPN, VPN Secure) select by <strong>region</strong> rather than
country, and unshackle converts Windscribe region names automatically. For instance <code>us</code>
becomes <code>US East</code> and <code>uk</code>/<code>gb</code> become <code>United Kingdom</code>.</p>
<h3 id="container-lifecycle">Container lifecycle<a class="headerlink" href="#container-lifecycle" title="Permanent link">&para;</a></h3>
<p>Understanding what Gluetun does behind the scenes helps when something goes wrong:</p>
<ol>
<li><strong>Image check.</strong> unshackle pulls <code>qmcgaw/gluetun:latest</code> if it is not already
present.</li>
<li><strong>Reuse.</strong> If a container for this exact <code>provider:region</code> already runs (from a
concurrent unshackle process), unshackle adopts it and does not start a new one. It
removes stopped or dead containers with the same name first.</li>
</ol>
<div class="admonition note">
<p class="admonition-title">Expect a slow first request, then instant reuse</p>
<p>Bringing up a fresh tunnel takes roughly <strong>10 to 30 seconds</strong> for the first request to a
given <code>provider:region</code>. This is the VPN handshake and readiness wait, not a hang.
Every subsequent request that reuses that same container is effectively <strong>instant</strong>. A
slow first run is normal.</p>
</div>
<ol>
<li><strong>Port allocation.</strong> unshackle chooses a free local port, starting from <code>base_port</code>.</li>
<li><strong>Start.</strong> unshackle launches the container bound to <code>127.0.0.1:{port}</code>. It passes
credentials through a temporary, <code>0600</code>-permission env-file (never as <code>-e</code> flags),
and it overwrites and deletes that file immediately after.</li>
<li><strong>Readiness wait.</strong> unshackle polls the container logs (up to 60 seconds) until the
HTTP proxy listens <strong>and</strong> the VPN reports a completed connection. Fatal errors like
<code>authentication failed</code> or <code>invalid credentials</code> abort early, and unshackle attaches
recent logs to the error to help you diagnose it.</li>
<li><strong>IP verification.</strong> When <code>verify_ip</code> is on, unshackle queries the container's exit IP
and checks the country matches what you asked for, raising a <em>region mismatch</em> error if
it does not.</li>
<li><strong>Proxy handed back.</strong> You get <code>http://localhost:{port}</code> (or with auth credentials if
configured).</li>
</ol>
<div class="admonition note">
<p class="admonition-title">Cleanup and Ctrl+C</p>
<p>unshackle cleans up containers with an <code>atexit</code> handler on normal exit. Gluetun
deliberately does <strong>not</strong> install its own signal handlers, so <code>Ctrl+C</code> continues to
work as you expect. If something kills a run hard (for example <code>kill -9</code>), a container
can stay behind. With <code>auto_cleanup: true</code> the next run that adopts that name still
works, or you can remove it manually with <code>docker rm -f</code>.</p>
</div>
<h3 id="troubleshooting">Troubleshooting<a class="headerlink" href="#troubleshooting" title="Permanent link">&para;</a></h3>
<div class="admonition example">
<p class="admonition-title">Common Gluetun issues</p>
<ul>
<li><strong><code>RuntimeError</code> about Docker not found.</strong> Install Docker and make sure that
<code>docker</code> is on your <code>PATH</code>.</li>
<li><strong><code>invalid credentials</code> / <code>authentication failed</code> in the readiness wait.</strong> Your
WireGuard/OpenVPN credentials are wrong or in the wrong fields. Double-check
<code>private_key</code>, <code>addresses</code>, and (for Windscribe) <code>preshared_key</code>.</li>
<li><strong>Region mismatch error.</strong> The VPN connected but exited in a different country than
requested. Examine your <code>server_countries</code> mapping, or set <code>verify_ip: false</code> if
you intentionally use a nearby region.</li>
<li><strong>Timeout after 60 seconds.</strong> The tunnel never came up. Inspect the container logs
(<code>docker logs &lt;container&gt;</code>) for the underlying VPN error.</li>
</ul>
</div>
<h2 id="expressvpn">ExpressVPN<a class="headerlink" href="#expressvpn" title="Permanent link">&para;</a></h2>
<p>ExpressVPN mirrors the ExpressVPN Android TV app: it signs in through Express's OAuth 2.0
device-login flow, obtains proxy-capable locations from Express's API, and returns an
authenticated HTTPS proxy. The device login runs once. After that, the cached refresh
token keeps you signed in headlessly.</p>
<p>Enable it and do a download interactively once:</p>
<div class="language-yaml highlight"><span class="filename">unshackle.yaml</span><pre><span></span><code><span id="__span-10-1"><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a><span class="nt">proxy_providers</span><span class="p">:</span>
</span><span id="__span-10-2"><a id="__codelineno-10-2" name="__codelineno-10-2" href="#__codelineno-10-2"></a><span class="w"> </span><span class="nt">expressvpn</span><span class="p">:</span>
</span><span id="__span-10-3"><a id="__codelineno-10-3" name="__codelineno-10-3" href="#__codelineno-10-3"></a><span class="w"> </span><span class="nt">enable</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span>
</span></code></pre></div>
<p>With <code>enable: true</code> and an interactive terminal, unshackle prints a code and a URL to
enter it at. Once you approve it, unshackle caches the tokens and reuses them
automatically, so later runs need no terminal. ExpressVPN also <strong>auto-loads</strong> once its token cache exists, so you can
drop <code>enable</code> after the first login, but you can keep a YAML block to pin regions or
servers:</p>
<div class="language-yaml highlight"><span class="filename">unshackle.yaml (optional)</span><pre><span></span><code><span id="__span-11-1"><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a><span class="nt">proxy_providers</span><span class="p">:</span>
</span><span id="__span-11-2"><a id="__codelineno-11-2" name="__codelineno-11-2" href="#__codelineno-11-2"></a><span class="w"> </span><span class="nt">expressvpn</span><span class="p">:</span>
</span><span id="__span-11-3"><a id="__codelineno-11-3" name="__codelineno-11-3" href="#__codelineno-11-3"></a><span class="w"> </span><span class="nt">region_map</span><span class="p">:</span>
</span><span id="__span-11-4"><a id="__codelineno-11-4" name="__codelineno-11-4" href="#__codelineno-11-4"></a><span class="w"> </span><span class="nt">us</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ny-02</span><span class="w"> </span><span class="c1"># prefer New York, server 02 for the US</span>
</span><span id="__span-11-5"><a id="__codelineno-11-5" name="__codelineno-11-5" href="#__codelineno-11-5"></a><span class="w"> </span><span class="nt">gb</span><span class="p">:</span><span class="w"> </span><span class="c1"># empty = smart random location</span>
</span><span id="__span-11-6"><a id="__codelineno-11-6" name="__codelineno-11-6" href="#__codelineno-11-6"></a><span class="w"> </span><span class="nt">server_map</span><span class="p">:</span>
</span><span id="__span-11-7"><a id="__codelineno-11-7" name="__codelineno-11-7" href="#__codelineno-11-7"></a><span class="w"> </span><span class="nt">myserver</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">usny-newyork-2</span>
</span></code></pre></div>
<p><strong>Default file location:</strong></p>
<table>
<thead>
<tr>
<th>File</th>
<th>Default path</th>
</tr>
</thead>
<tbody>
<tr>
<td>Token cache</td>
<td><code>{cache}/vpn/expressvpn_tokens.json</code></td>
</tr>
</tbody>
</table>
<p>Where <code>{cache}</code> is your configured cache directory.</p>
<p><strong>Query forms:</strong></p>
<table>
<thead>
<tr>
<th>Query</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>us</code></td>
<td>A smart, random US location.</td>
</tr>
<tr>
<td><code>us-ny</code></td>
<td>US, New York.</td>
</tr>
<tr>
<td><code>us-ny-2</code> / <code>us-ny2</code></td>
<td>US, New York, the 2<sup>nd</sup> server.</td>
</tr>
<tr>
<td><code>usny-newyork-2</code></td>
<td>A full location slug.</td>
</tr>
<tr>
<td><code>hostname.expressprovider.com</code></td>
<td>A direct endpoint hostname.</td>
</tr>
</tbody>
</table>
<p>The resolved proxy is an HTTPS proxy on port <code>443</code>. On success, unshackle prints a
location summary such as <code>(USA - New York, #3 of 5): .214</code>.</p>
<div class="admonition note">
<p class="admonition-title">Cache safety</p>
<p>unshackle writes ExpressVPN's token cache with <code>0600</code> permissions, so the refresh
token is never briefly world-readable.</p>
</div>
<h2 id="nordvpn">NordVPN<a class="headerlink" href="#nordvpn" title="Permanent link">&para;</a></h2>
<p>NordVPN uses your <strong>Service Credentials</strong>, not your normal NordVPN login. Make them
from the <a href="https://my.nordaccount.com/dashboard/nordvpn/">NordVPN dashboard</a> under manual
setup. They are a long random username/password pair, not your email and password.</p>
<div class="language-yaml highlight"><span class="filename">unshackle.yaml</span><pre><span></span><code><span id="__span-12-1"><a id="__codelineno-12-1" name="__codelineno-12-1" href="#__codelineno-12-1"></a><span class="nt">proxy_providers</span><span class="p">:</span>
</span><span id="__span-12-2"><a id="__codelineno-12-2" name="__codelineno-12-2" href="#__codelineno-12-2"></a><span class="w"> </span><span class="nt">nordvpn</span><span class="p">:</span>
</span><span id="__span-12-3"><a id="__codelineno-12-3" name="__codelineno-12-3" href="#__codelineno-12-3"></a><span class="w"> </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">YOUR_SERVICE_USERNAME</span>
</span><span id="__span-12-4"><a id="__codelineno-12-4" name="__codelineno-12-4" href="#__codelineno-12-4"></a><span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">YOUR_SERVICE_PASSWORD</span>
</span><span id="__span-12-5"><a id="__codelineno-12-5" name="__codelineno-12-5" href="#__codelineno-12-5"></a><span class="w"> </span><span class="nt">server_map</span><span class="p">:</span><span class="w"> </span><span class="c1"># optional: pin specific server IDs</span>
</span><span id="__span-12-6"><a id="__codelineno-12-6" name="__codelineno-12-6" href="#__codelineno-12-6"></a><span class="w"> </span><span class="nt">us</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">1234</span>
</span><span id="__span-12-7"><a id="__codelineno-12-7" name="__codelineno-12-7" href="#__codelineno-12-7"></a><span class="w"> </span><span class="nt">us:seattle</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">5678</span>
</span></code></pre></div>
<p><strong>Required keys:</strong> <code>username</code>, <code>password</code>. unshackle validates them up front: the
combined username and password must be exactly 48 alphanumeric characters (case-insensitive), and
the username must not contain an <code>@</code>. If validation fails, you have almost certainly used
your login credentials instead of your service credentials.</p>
<p><code>server_map</code> is optional and maps a region (or <code>code:city</code>) to a pinned NordVPN server ID.
Its main use is to tune performance: for a given streaming service one specific server
often performs noticeably better than NordVPN's recommended pick. So you can hard-code
that server for a day or two as a workaround, until it degrades and you swap in another.</p>
<div class="admonition note">
<p class="admonition-title">Use <code>gb</code>, not <code>uk</code>, for the United Kingdom</p>
<p>Query the UK as <code>gb</code>. unshackle uses the Alpha-2 country code <code>gb</code> deliberately to stay
consistent with its other regional/country-code systems and avoid the language-vs-
country-code confusion that <code>uk</code> invites.</p>
</div>
<p><strong>Query forms:</strong></p>
<table>
<thead>
<tr>
<th>Query</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>us</code></td>
<td>A recommended US server.</td>
</tr>
<tr>
<td><code>us1234</code></td>
<td>The specific server <code>us1234</code>.</td>
</tr>
<tr>
<td><code>us:seattle</code></td>
<td>A recommended server in Seattle.</td>
</tr>
<tr>
<td><code>228</code></td>
<td>A NordVPN numeric country ID.</td>
</tr>
</tbody>
</table>
<p>The returned proxy is HTTPS on <strong>port 89</strong> (NordVPN disabled its plain-HTTP proxies on
port 80 in 2021).</p>
<div class="language-shell highlight"><pre><span></span><code><span id="__span-13-1"><a id="__codelineno-13-1" name="__codelineno-13-1" href="#__codelineno-13-1"></a>unshackle<span class="w"> </span>dl<span class="w"> </span>--proxy<span class="w"> </span>nordvpn:us:seattle<span class="w"> </span>EXAMPLE<span class="w"> </span><span class="m">81234567</span>
</span></code></pre></div>
<h2 id="proton-vpn">Proton VPN<a class="headerlink" href="#proton-vpn" title="Permanent link">&para;</a></h2>
<p>Proton VPN supports two authentication methods, and unshackle can work with either. Note
that free Proton accounts work, but Proton limits them to its free-tier exit countries.</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:2"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">TV login (recommended)</label><label for="__tabbed_2_2">Exported cookies</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
<p>Proton's TV device-login flow gives a self-sustaining, refreshable sign-in. This is
the only method that can refresh itself headlessly, so it keeps working without
re-exporting anything. Enable it and do a download interactively once:</p>
<div class="language-yaml highlight"><span class="filename">unshackle.yaml</span><pre><span></span><code><span id="__span-14-1"><a id="__codelineno-14-1" name="__codelineno-14-1" href="#__codelineno-14-1"></a><span class="nt">proxy_providers</span><span class="p">:</span>
</span><span id="__span-14-2"><a id="__codelineno-14-2" name="__codelineno-14-2" href="#__codelineno-14-2"></a><span class="w"> </span><span class="nt">protonvpn</span><span class="p">:</span>
</span><span id="__span-14-3"><a id="__codelineno-14-3" name="__codelineno-14-3" href="#__codelineno-14-3"></a><span class="w"> </span><span class="nt">enable</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span>
</span></code></pre></div>
<p>With <code>enable: true</code> and an interactive terminal, unshackle prints a code and asks you
to enter it at <a href="https://account.proton.me/vpn/tv/code">account.proton.me/vpn/tv/code</a>.
Once you approve it, unshackle caches the sign-in and reuses it automatically.</p>
</div>
<div class="tabbed-block">
<p>Export your <code>account.proton.me</code> cookies (the <code>AUTH-&lt;UID&gt;</code> cookie) to the Proton
cookie file. This method is <strong>access-only</strong>: it cannot refresh, so you must re-export
when it expires.</p>
<div class="admonition warning">
<p class="admonition-title">The <code>AUTH-&lt;UID&gt;</code> cookie is HttpOnly</p>
<p>This is the single most common cause of a failed Proton cookie import: the required
<code>AUTH-&lt;UID&gt;</code> cookie is <strong>HttpOnly</strong>, and many one-click cookie-export extensions
silently skip HttpOnly cookies by default. If your import "does not work," use an
exporter that explicitly includes HttpOnly cookies.</p>
</div>
<div class="admonition note">
<p class="admonition-title">Why this session can't refresh, and lapses after ~24h</p>
<p>Proton rotates browser refresh tokens and ties them to the live browser, so a
headless refresh is impossible. unshackle therefore deliberately uses only the
cookie's short-lived <strong>access token</strong> and never attempts a refresh. That is why
the sign-in lapses at Proton's ~24h TTL and you must export it again. This is
exactly why TV login is the better method for set-and-forget use.</p>
</div>
</div>
</div>
</div>
<p><strong>Default file locations:</strong></p>
<table>
<thead>
<tr>
<th>File</th>
<th>Default path</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cookies</td>
<td><code>{cookies}/vpn/protonvpn.txt</code></td>
</tr>
<tr>
<td>Session cache</td>
<td><code>{cache}/vpn/protonvpn.json</code></td>
</tr>
</tbody>
</table>
<p>Proton VPN <strong>auto-loads</strong> if its cookie file exists. Like ExpressVPN, unshackle writes
its cache file with <code>0600</code> permissions.</p>
<div class="admonition note">
<p class="admonition-title">Cache vs cookie precedence</p>
<p>If both a TV-login cache and a cookie file exist, unshackle <strong>prefers the cache</strong>
unless the cookie file is newer. unshackle reads cookies fresh every run and never
writes them back to the cache. So to deliberately force use of the cookie file,
re-save (re-export) it so its modification time is newer than the cache.</p>
</div>
<p><strong>Query forms (after the <code>protonvpn:</code> prefix):</strong></p>
<table>
<thead>
<tr>
<th>Query</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>us</code></td>
<td>A random US server (paid tiers preferred over free).</td>
</tr>
<tr>
<td><code>us12</code></td>
<td>Proton US server #12.</td>
</tr>
<tr>
<td><code>us:ny</code></td>
<td>A server in New York.</td>
</tr>
</tbody>
</table>
<p>The returned proxy is HTTPS on <strong>port 4443</strong> (or <strong>port 443</strong> for Secure Core servers).
unshackle excludes Secure Core and Tor servers from selection because they are too slow
for a plain proxy. On success, unshackle prints a summary such as <code>(Name - City): server-host</code>.</p>
<div class="admonition tip">
<p class="admonition-title">Proton server numbers aren't sequential</p>
<p>When pinning a server with <code>protonvpn:deNN</code>, do not guess a low number. A country may
have non-contiguous IDs like <code>#203</code>, <code>#813</code>, and so on. Copy the number from a
previous run's log line (e.g. <code>DE#203</code>) instead.</p>
</div>
<h2 id="hola">Hola<a class="headerlink" href="#hola" title="Permanent link">&para;</a></h2>
<p>Hola requires no configuration at all. It uses the
<a href="https://github.com/Snawoot/hola-proxy"><code>hola-proxy</code></a> binary, and <strong>auto-loads</strong> whenever
that binary is found on your <code>PATH</code>. If you query Hola but the binary is missing, the
proxy provider raises an error that tells you to install it.</p>
<div class="language-shell highlight"><span class="filename">Install hola-proxy, then just query a country</span><pre><span></span><code><span id="__span-15-1"><a id="__codelineno-15-1" name="__codelineno-15-1" href="#__codelineno-15-1"></a>unshackle<span class="w"> </span>dl<span class="w"> </span>--proxy<span class="w"> </span>hola:us<span class="w"> </span>EXAMPLE<span class="w"> </span><span class="m">81234567</span>
</span></code></pre></div>
<p><strong>Query forms:</strong> a two-letter country code (e.g. <code>us</code>, <code>gb</code>). unshackle asks <code>hola-proxy</code>
for available proxies in that country and picks one at random, returning an HTTP proxy.</p>
<div class="admonition warning">
<p class="admonition-title">Temporary bans</p>
<p>Hola's free tier can rate-limit or temporarily ban you if queried too aggressively. If
you see a <em>temporary ban detected</em> error, wait before retrying. Hola currently uses
only datacenter proxies.</p>
</div>
<h2 id="surfshark">Surfshark<a class="headerlink" href="#surfshark" title="Permanent link">&para;</a></h2>
<p>Surfshark, like NordVPN, uses <strong>Service Credentials</strong> rather than your login. Make
them from the <a href="https://my.surfshark.com/vpn/manual-setup/main/openvpn">Surfshark manual-setup page</a>.</p>
<div class="language-yaml highlight"><span class="filename">unshackle.yaml</span><pre><span></span><code><span id="__span-16-1"><a id="__codelineno-16-1" name="__codelineno-16-1" href="#__codelineno-16-1"></a><span class="nt">proxy_providers</span><span class="p">:</span>
</span><span id="__span-16-2"><a id="__codelineno-16-2" name="__codelineno-16-2" href="#__codelineno-16-2"></a><span class="w"> </span><span class="nt">surfsharkvpn</span><span class="p">:</span>
</span><span id="__span-16-3"><a id="__codelineno-16-3" name="__codelineno-16-3" href="#__codelineno-16-3"></a><span class="w"> </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">YOUR_SERVICE_USERNAME</span>
</span><span id="__span-16-4"><a id="__codelineno-16-4" name="__codelineno-16-4" href="#__codelineno-16-4"></a><span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">YOUR_SERVICE_PASSWORD</span>
</span><span id="__span-16-5"><a id="__codelineno-16-5" name="__codelineno-16-5" href="#__codelineno-16-5"></a><span class="w"> </span><span class="nt">server_map</span><span class="p">:</span><span class="w"> </span><span class="c1"># optional</span>
</span><span id="__span-16-6"><a id="__codelineno-16-6" name="__codelineno-16-6" href="#__codelineno-16-6"></a><span class="w"> </span><span class="nt">us</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">1234</span>
</span></code></pre></div>
<p><strong>Required keys:</strong> <code>username</code>, <code>password</code>, validated with the same 48-character rule as
NordVPN (combined username+password must be 48 alphanumeric characters, case-insensitive, no <code>@</code>).
<code>server_map</code> optionally pins server IDs per region.</p>
<p><strong>Query forms:</strong></p>
<table>
<thead>
<tr>
<th>Query</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>us</code></td>
<td>A random US server.</td>
</tr>
<tr>
<td><code>us-bos</code></td>
<td>A specific server.</td>
</tr>
<tr>
<td><code>us:seattle</code></td>
<td>A random server in Seattle.</td>
</tr>
</tbody>
</table>
<p>The returned proxy is HTTPS on <strong>port 443</strong>.</p>
<h2 id="windscribe">Windscribe<a class="headerlink" href="#windscribe" title="Permanent link">&para;</a></h2>
<p>Windscribe uses <strong>Service Credentials</strong> from its
<a href="https://windscribe.com/getconfig/openvpn">OpenVPN config page</a>. unshackle only makes
sure that a username and a password are present (there is no 48-character rule).</p>
<div class="language-yaml highlight"><span class="filename">unshackle.yaml</span><pre><span></span><code><span id="__span-17-1"><a id="__codelineno-17-1" name="__codelineno-17-1" href="#__codelineno-17-1"></a><span class="nt">proxy_providers</span><span class="p">:</span>
</span><span id="__span-17-2"><a id="__codelineno-17-2" name="__codelineno-17-2" href="#__codelineno-17-2"></a><span class="w"> </span><span class="nt">windscribevpn</span><span class="p">:</span>
</span><span id="__span-17-3"><a id="__codelineno-17-3" name="__codelineno-17-3" href="#__codelineno-17-3"></a><span class="w"> </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">YOUR_SERVICE_USERNAME</span>
</span><span id="__span-17-4"><a id="__codelineno-17-4" name="__codelineno-17-4" href="#__codelineno-17-4"></a><span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">YOUR_SERVICE_PASSWORD</span>
</span><span id="__span-17-5"><a id="__codelineno-17-5" name="__codelineno-17-5" href="#__codelineno-17-5"></a><span class="w"> </span><span class="nt">server_map</span><span class="p">:</span><span class="w"> </span><span class="c1"># optional: alias -&gt; hostname</span>
</span><span id="__span-17-6"><a id="__codelineno-17-6" name="__codelineno-17-6" href="#__codelineno-17-6"></a><span class="w"> </span><span class="nt">us</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">us-east.totallyacdn.com</span>
</span><span id="__span-17-7"><a id="__codelineno-17-7" name="__codelineno-17-7" href="#__codelineno-17-7"></a><span class="w"> </span><span class="nt">us:seattle</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">us-seattle.totallyacdn.com</span>
</span></code></pre></div>
<p><strong>Required keys:</strong> <code>username</code>, <code>password</code>. <code>server_map</code> optionally maps a region (or
<code>region:city</code>) directly to a hostname.</p>
<p><strong>Query forms:</strong></p>
<table>
<thead>
<tr>
<th>Query</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>us</code></td>
<td>A random US server.</td>
</tr>
<tr>
<td><code>us150</code> / <code>sg007</code></td>
<td>A specific numbered server.</td>
</tr>
<tr>
<td><code>us:seattle</code></td>
<td>A random server in Seattle.</td>
</tr>
</tbody>
</table>
<p>The returned proxy is HTTPS on <strong>port 443</strong>.</p>
<div class="admonition warning">
<p class="admonition-title">Windscribe is not loaded by the REST API resolver</p>
<p>The <code>dl</code> command loads the standalone Windscribe proxy provider (and Gluetun), but
the proxy resolver that the REST API and the remote-download client use does <strong>not</strong>.
If you drive downloads through the API and need Windscribe or Gluetun exits, get the
proxy on the CLI side, or use a different proxy provider for API jobs. See the
developer note below.</p>
</div>
<h2 id="developer-notes">Developer notes<a class="headerlink" href="#developer-notes" title="Permanent link">&para;</a></h2>
<div class="admonition note">
<p class="admonition-title">Two resolution paths (developers)</p>
<p>unshackle has <strong>two</strong> proxy-resolution code paths that are intentionally not
identical:</p>
<ul>
<li><code>unshackle/commands/dl.py</code> inlines its own logic and loads <strong>all eight</strong> providers,
including Windscribe and Gluetun.</li>
<li><code>unshackle/core/proxies/resolve.py::initialize_proxy_providers()</code> is the shared
resolver used by the REST API handlers and the remote-service client. It <strong>omits</strong>
Windscribe and Gluetun.</li>
</ul>
<p>Both paths accept the same query grammar (a direct URI, a <code>provider:query</code>, or a bare
country query tried against providers in order), but the set of available providers
differs. Keep this in mind when you add a proxy provider: an entry in <code>dl.py</code> does not
make it available over the API, and the opposite is also true.</p>
</div>
<div class="admonition note">
<p class="admonition-title">Implementing a new provider (developers)</p>
<p>Every proxy provider subclasses <code>unshackle.core.proxies.proxy.Proxy</code> and has three
methods:</p>
<ul>
<li><code>__init__(self, **kwargs)</code> receives its <code>proxy_providers</code> sub-dict splatted as
keyword arguments, so YAML keys map 1:1 to constructor parameters. Do authentication
and catalogue pre-fetching here.</li>
<li><code>__repr__(self)</code> is a human summary, conventionally <code>"{n} Countries ({m} Servers)"</code>.</li>
<li><code>get_proxy(self, query)</code> returns a Requests-compatible proxy URI, or <code>None</code> only
when the query was valid but no proxy exists. Raise on genuine errors or bad queries.</li>
</ul>
<p>Optionally add <code>get_connection_info(query)</code> (as Gluetun does) or
<code>last_connection_display()</code> (as ExpressVPN and Proton VPN do) to make <code>dl</code> print a
friendly connection line instead of the raw proxy URL.</p>
</div>
<h2 id="see-also">See also<a class="headerlink" href="#see-also" title="Permanent link">&para;</a></h2>
<ul>
<li><a href="../downloading/">Downloading</a>: the full <code>dl</code> command guide, including where proxy
flags sit in the download flow.</li>
<li><a href="../../getting-started/configuration-file/">Configuration file</a>: the <code>unshackle.yaml</code>
reference, including directories for cookies and cache used by the cookie-based
providers.</li>
<li><a href="../../reference/api/config/">Configuration reference</a>: the developer-facing
configuration API, including the <code>proxy_providers</code> structure.</li>
</ul>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<nav class="md-footer__inner md-grid" aria-label="Footer" >
<a href="../drm-and-cdm/" class="md-footer__link md-footer__link--prev" aria-label="Previous: DRM &amp; CDM Setup">
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</div>
<div class="md-footer__title">
<span class="md-footer__direction">
Previous
</span>
<div class="md-ellipsis">
DRM & CDM Setup
</div>
</div>
</a>
<a href="../vaults/" class="md-footer__link md-footer__link--next" aria-label="Next: Key Vaults">
<div class="md-footer__title">
<span class="md-footer__direction">
Next
</span>
<div class="md-ellipsis">
Key Vaults
</div>
</div>
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg>
</div>
</a>
</nav>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "../..", "features": ["content.code.annotate", "content.code.copy", "content.tabs.link", "content.tooltips", "navigation.footer", "navigation.indexes", "navigation.sections", "navigation.tabs", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../assets/javascripts/bundle.d7400e89.min.js"></script>
</body>
</html>