pikvm/tailscale/index.html

3049 lines
65 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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="How to configure the access to your PiKVM using Tailscale VPN">
<meta name="author" content="Maxim Devaev">
<link rel="canonical" href="https://pikvm.github.io/pikvm/tailscale/">
<link rel="prev" href="../reverse_proxy/">
<link rel="next" href="../cloudflared/">
<link rel="icon" href="../_assets/favicon.ico">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.22">
<title>Tailscale VPN - PiKVM Handbook</title>
<link rel="stylesheet" href="../assets/stylesheets/main.84d31ad4.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=arial,+sans-serif:300,300i,400,400i,700,700i%7Cmonospace:400,400i,700,700i&amp;display=fallback">
<style>:root{--md-text-font:"arial, sans-serif";--md-code-font:"monospace"}</style>
<link rel="stylesheet" href="../_assets/user.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>
<link href="../assets/stylesheets/glightbox.min.css" rel="stylesheet"><script src="../assets/javascripts/glightbox.min.js"></script><style id="glightbox-style">
html.glightbox-open { overflow: initial; height: 100%; }
.gslide-title { margin-top: 0px; user-select: text; }
.gslide-desc { color: #666; user-select: text; }
.gslide-image img { background: white; }
.gscrollbar-fixer { padding-right: 15px; }
.gdesc-inner { font-size: 0.75rem; }
body[data-md-color-scheme="slate"] .gdesc-inner { background: var(--md-default-bg-color); }
body[data-md-color-scheme="slate"] .gslide-title { color: var(--md-default-fg-color); }
body[data-md-color-scheme="slate"] .gslide-desc { color: var(--md-default-fg-color); }
</style></head>
<body dir="ltr">
<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="#configuring-the-pikvm" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="PiKVM Handbook" class="md-header__button md-logo" aria-label="PiKVM Handbook" data-md-component="logo">
<img src="../_assets/logo.png" alt="logo">
</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"></path></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">
PiKVM Handbook
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Tailscale VPN
</span>
</div>
</div>
</div>
<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"></path></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"></path></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"></path></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"></path></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"></path></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/pikvm/pikvm" 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"></path></svg>
</div>
<div class="md-source__repository">
pikvm/pikvm
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<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" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="PiKVM Handbook" class="md-nav__button md-logo" aria-label="PiKVM Handbook" data-md-component="logo">
<img src="../_assets/logo.png" alt="logo">
</a>
PiKVM Handbook
</label>
<div class="md-nav__source">
<a href="https://github.com/pikvm/pikvm" 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"></path></svg>
</div>
<div class="md-source__repository">
pikvm/pikvm
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_1">
<label class="md-nav__link" for="__nav_1" id="__nav_1_label" tabindex="">
<span class="md-ellipsis">
Device guides
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_1_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_1">
<span class="md-nav__icon md-icon"></span>
Device guides
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../v4/" class="md-nav__link">
<span class="md-ellipsis">
PiKVM V4 Mini &amp; Plus
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../v3/" class="md-nav__link">
<span class="md-ellipsis">
PiKVM V3
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../switch/" class="md-nav__link">
<span class="md-ellipsis">
PiKVM Switch
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../v2/" class="md-nav__link">
<span class="md-ellipsis">
DIY PiKVM V2
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../v1/" class="md-nav__link">
<span class="md-ellipsis">
DIY PiKVM V1
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2">
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="">
<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="../cheatsheet/" class="md-nav__link">
<span class="md-ellipsis">
Cheat Sheet
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../webui/" class="md-nav__link">
<span class="md-ellipsis">
Web UI Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../config/" class="md-nav__link">
<span class="md-ellipsis">
Configuring PiKVM
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../auth/" class="md-nav__link">
<span class="md-ellipsis">
Authentication &amp; 2FA
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../faq/" class="md-nav__link">
<span class="md-ellipsis">
FAQ &amp; Troubleshooting
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../flashing_os/" class="md-nav__link">
<span class="md-ellipsis">
Flashing OS
</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">
Networking
</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>
Networking
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3_1" checked>
<label class="md-nav__link" for="__nav_3_1" id="__nav_3_1_label" tabindex="0">
<span class="md-ellipsis">
Internet access
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_3_1_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_3_1">
<span class="md-nav__icon md-icon"></span>
Internet access
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../port_forwarding/" class="md-nav__link">
<span class="md-ellipsis">
Port forwarding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../reverse_proxy/" class="md-nav__link">
<span class="md-ellipsis">
Reverse proxy
</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">
Tailscale 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">
Tailscale 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="#configuring-the-pikvm" class="md-nav__link">
<span class="md-ellipsis">
Configuring the PiKVM
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#configuring-a-client-device" class="md-nav__link">
<span class="md-ellipsis">
Configuring a client device
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#using-tailscale-certificates" class="md-nav__link">
<span class="md-ellipsis">
Using Tailscale Certificates
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#automated-ephemeral-tailscale-certificates-renewal" class="md-nav__link">
<span class="md-ellipsis">
Automated Ephemeral Tailscale Certificates Renewal
</span>
</a>
<nav class="md-nav" aria-label="Automated Ephemeral Tailscale Certificates Renewal">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#boot-sequence-recap" class="md-nav__link">
<span class="md-ellipsis">
Boot sequence recap:
</span>
</a>
</li>
</ul>
</nav>
</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="../cloudflared/" class="md-nav__link">
<span class="md-ellipsis">
Cloudflare Tunnel
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../wifi/" class="md-nav__link">
<span class="md-ellipsis">
Setting up Wi-Fi
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../modem/" class="md-nav__link">
<span class="md-ellipsis">
Setting up 3G/4G/LTE modem
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../letsencrypt/" class="md-nav__link">
<span class="md-ellipsis">
Let's Encrypt certificates
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_4">
<label class="md-nav__link" for="__nav_4" id="__nav_4_label" tabindex="">
<span class="md-ellipsis">
Video
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<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>
Video
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../video/" class="md-nav__link">
<span class="md-ellipsis">
Video modes (WebRTC, Direct)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../webrtc_config/" class="md-nav__link">
<span class="md-ellipsis">
WebRTC configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../edid/" class="md-nav__link">
<span class="md-ellipsis">
Tuning HDMI EDID
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../pass/" class="md-nav__link">
<span class="md-ellipsis">
HDMI passthrough
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_5">
<label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="">
<span class="md-ellipsis">
Peripheral devices
</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>
Peripheral devices
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../atx_board/" class="md-nav__link">
<span class="md-ellipsis">
ATX board
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../usb/" class="md-nav__link">
<span class="md-ellipsis">
USB configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../usb_pass/" class="md-nav__link">
<span class="md-ellipsis">
USB passthrough
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../audio/" class="md-nav__link">
<span class="md-ellipsis">
Audio / Microphone
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_5_5">
<label class="md-nav__link" for="__nav_5_5" id="__nav_5_5_label" tabindex="0">
<span class="md-ellipsis">
Keyboard &amp; mouse
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_5_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5_5">
<span class="md-nav__icon md-icon"></span>
Keyboard &amp; mouse
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../mouse/" class="md-nav__link">
<span class="md-ellipsis">
Mouse modes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../mouse_jiggler/" class="md-nav__link">
<span class="md-ellipsis">
Mouse jiggler
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../pico_hid/" class="md-nav__link">
<span class="md-ellipsis">
Pico HID (USB, PS/2)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../bluetooth_hid/" class="md-nav__link">
<span class="md-ellipsis">
Bluetooth HID
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../msd/" class="md-nav__link">
<span class="md-ellipsis">
Mass Storage Drive
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../usb_ethernet/" class="md-nav__link">
<span class="md-ellipsis">
Ethernet-over-USB
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../usb_serial/" class="md-nav__link">
<span class="md-ellipsis">
Serial-over-USB
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../gpio/" class="md-nav__link">
<span class="md-ellipsis">
GPIO (pins, relays, lamps, etc)
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_6">
<label class="md-nav__link" for="__nav_6" id="__nav_6_label" tabindex="">
<span class="md-ellipsis">
Advanced usage
</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>
Advanced usage
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../auth_advanced/" class="md-nav__link">
<span class="md-ellipsis">
Advanced authentication
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../id/" class="md-nav__link">
<span class="md-ellipsis">
PiKVM identification
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../on_boot_config/" class="md-nav__link">
<span class="md-ellipsis">
On-boot configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../auto_snapshots/" class="md-nav__link">
<span class="md-ellipsis">
Automatic snapshots
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../vnc/" class="md-nav__link">
<span class="md-ellipsis">
Using VNC
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../multiport/" class="md-nav__link">
<span class="md-ellipsis">
Multiport KVM-over-IP
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../wol/" class="md-nav__link">
<span class="md-ellipsis">
Wake-on-LAN the server
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../ipmi/" class="md-nav__link">
<span class="md-ellipsis">
IPMI &amp; Redfish integration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../pst/" class="md-nav__link">
<span class="md-ellipsis">
Persistent storage
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../prometheus/" class="md-nav__link">
<span class="md-ellipsis">
Prometheus monitoring
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_7">
<label class="md-nav__link" for="__nav_7" id="__nav_7_label" tabindex="">
<span class="md-ellipsis">
Development
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_7_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_7">
<span class="md-nav__icon md-icon"></span>
Development
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../api/" class="md-nav__link">
<span class="md-ellipsis">
HTTP API reference
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../building_os/" class="md-nav__link">
<span class="md-ellipsis">
Building PiKVM OS
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../3d_printing/" class="md-nav__link">
<span class="md-ellipsis">
Cases for 3D printing
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_8">
<label class="md-nav__link" for="__nav_8" id="__nav_8_label" tabindex="">
<span class="md-ellipsis">
Legacy
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_8_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_8">
<span class="md-nav__icon md-icon"></span>
Legacy
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../arduino_hid/" class="md-nav__link">
<span class="md-ellipsis">
Arduino HID
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../msd_legacy/" class="md-nav__link">
<span class="md-ellipsis">
Big DVD images on old PiKVM
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_9">
<label class="md-nav__link" for="__nav_9" id="__nav_9_label" tabindex="">
<span class="md-ellipsis">
PiKVM Info
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_9_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_9">
<span class="md-nav__icon md-icon"></span>
PiKVM Info
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../compliance/" class="md-nav__link">
<span class="md-ellipsis">
Compliance
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_9_2">
<div class="md-nav__link md-nav__container">
<a href="../blog/" class="md-nav__link ">
<span class="md-ellipsis">
Blog &amp; News
</span>
</a>
<label class="md-nav__link " for="__nav_9_2" id="__nav_9_2_label" tabindex="0">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_9_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_9_2">
<span class="md-nav__icon md-icon"></span>
Blog &amp; News
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_9_2_2">
<label class="md-nav__link" for="__nav_9_2_2" id="__nav_9_2_2_label" tabindex="0">
<span class="md-ellipsis">
Archive
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="3" aria-labelledby="__nav_9_2_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_9_2_2">
<span class="md-nav__icon md-icon"></span>
Archive
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../blog/archive/2025/" class="md-nav__link">
<span class="md-ellipsis">
2025
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../blog/archive/2024/" class="md-nav__link">
<span class="md-ellipsis">
2024
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../blog/archive/2023/" class="md-nav__link">
<span class="md-ellipsis">
2023
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../blog/archive/2022/" class="md-nav__link">
<span class="md-ellipsis">
2022
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../blog/archive/2021/" class="md-nav__link">
<span class="md-ellipsis">
2021
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../blog/archive/2020/" class="md-nav__link">
<span class="md-ellipsis">
2020
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_9_2_3">
<label class="md-nav__link" for="__nav_9_2_3" id="__nav_9_2_3_label" tabindex="0">
<span class="md-ellipsis">
Categories
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="3" aria-labelledby="__nav_9_2_3_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_9_2_3">
<span class="md-nav__icon md-icon"></span>
Categories
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../blog/category/development/" class="md-nav__link">
<span class="md-ellipsis">
Development
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../blog/category/products/" class="md-nav__link">
<span class="md-ellipsis">
Products
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../blog/category/releases/" class="md-nav__link">
<span class="md-ellipsis">
Releases
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</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="#configuring-the-pikvm" class="md-nav__link">
<span class="md-ellipsis">
Configuring the PiKVM
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#configuring-a-client-device" class="md-nav__link">
<span class="md-ellipsis">
Configuring a client device
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#using-tailscale-certificates" class="md-nav__link">
<span class="md-ellipsis">
Using Tailscale Certificates
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#automated-ephemeral-tailscale-certificates-renewal" class="md-nav__link">
<span class="md-ellipsis">
Automated Ephemeral Tailscale Certificates Renewal
</span>
</a>
<nav class="md-nav" aria-label="Automated Ephemeral Tailscale Certificates Renewal">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#boot-sequence-recap" class="md-nav__link">
<span class="md-ellipsis">
Boot sequence recap:
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1>Tailscale VPN</h1>
<div><p>The <a href="https://tailscale.com/">Tailscale VPN</a> can be used to access PiKVM
from the Internet if configuring <a href="../port_forwarding/">port forwarding</a>
is not possible or more security is desired. Tailscale is a convenient
and free (for private use) tool for organizing a small VPN network.</p>
<p>The basic Tailscale configuration commands are shown below. For detailed
instructions, refer to <a href="https://tailscale.com/contact/support/">Tailscale
support</a>.</p>
<hr>
<h2 id="configuring-the-pikvm">Configuring the PiKVM<a class="headerlink" href="#configuring-the-pikvm" title="Permanent link"></a></h2>
<ol>
<li>
<p>Update OS:</p>
<details class="note">
<summary>Updating PiKVM OS</summary>
<p>To update, run following commands under the <code>root</code> user:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>pikvm-update
</code></pre></div>
<p>If you encounter an error like:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>pikvm-update
<span class="go">bash: pikvm-update: command not found</span>
</code></pre></div>
<p>It's most likely you have an old OS release. You can update the OS as follows:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>rw
<span class="gp">[root@pikvm ~]# </span>pacman<span class="w"> </span>-Syy
<span class="gp">[root@pikvm ~]# </span>pacman<span class="w"> </span>-S<span class="w"> </span>pikvm-os-updater
<span class="gp">[root@pikvm ~]# </span>pikvm-update
</code></pre></div>
<p>Next time you will be able to use the usual method with <code>pikvm-update</code>.</p>
</details>
</li>
<li>
<p>Install the Tailscale client, run <code>tailscaled</code> service and register it in the network:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>rw
<span class="gp"># </span>If<span class="w"> </span>you<span class="w"> </span>were<span class="w"> </span>afraid<span class="w"> </span>to<span class="w"> </span>pikvm-update<span class="w"> </span>above<span class="w"> </span>first<span class="w"> </span>run<span class="w"> </span>pacman<span class="w"> </span>-Syy
<span class="gp">[root@pikvm ~]# </span>pacman<span class="w"> </span>-S<span class="w"> </span>tailscale-pikvm
<span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>--now<span class="w"> </span>tailscaled
<span class="gp">[root@pikvm ~]# </span>tailscale<span class="w"> </span>up
</code></pre></div>
</li>
<li>
<p>Follow the link to authorize this installation.
You likely want to <a href="https://tailscale.com/kb/1028/key-expiry/">disable key expiry</a>!</p>
</li>
<li>
<p>After authorization success, reboot to make sure that everything works correctly:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>reboot
</code></pre></div>
</li>
<li>
<p>Now, you can view the IP address of the Tailscale network interface:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>ip<span class="w"> </span>addr<span class="w"> </span>show<span class="w"> </span>tailscale0
</code></pre></div>
</li>
</ol>
<p>If everything is successful, PiKVM will become a member of your VPN network.</p>
<div class="admonition warning">
<p class="admonition-title">Do not update Tailscale if you don't have access to PiKVM without VPN</p>
<p>Unfortunately, sometimes, updating the Tailscale client can cause problems due to
breaking changes. These are compatibility issues on the Tailscale side.
Remember this when updating.</p>
</div>
<hr>
<h2 id="configuring-a-client-device">Configuring a client device<a class="headerlink" href="#configuring-a-client-device" title="Permanent link"></a></h2>
<ul>
<li><a href="https://tailscale.com/download">Download</a> and install the Tailscale client
to the system you are using (not to the system you want to control).</li>
<li>Check the <a href="https://login.tailscale.com/admin/machines">Tailscale admin page</a> to view your VPN network.</li>
<li>Follow the URL in the web browser: <code>https://&lt;tailscale_kvm_ip&gt;</code> and you will see the PiKVM web interface.</li>
</ul>
<hr>
<h2 id="using-tailscale-certificates">Using Tailscale Certificates<a class="headerlink" href="#using-tailscale-certificates" title="Permanent link"></a></h2>
<p>PiKVM uses self-signed SSL certificates out of the box. You can also use
<a href="https://tailscale.com/kb/1153/enabling-https">Tailscale certificates</a> in place of the default one.</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>Tailscale certificates are provided by Let's Encrypt and has a default
<a href="https://letsencrypt.org/2015/11/09/why-90-days/">expiry of 90 days</a>.
There is currently no mechanism available to auto-renew Tailscale
certificate. You may put the commands below in a script to simplify
process.</p>
</div>
<ol>
<li>
<p>Switch filesystem to RW if in ReadOnly mode and delete existing PiKVM certificates for nginx and vnc.</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>rw
<span class="gp">[root@pikvm ~]# </span>rm<span class="w"> </span>-v<span class="w"> </span>/etc/kvmd/<span class="o">{</span>nginx,vnc<span class="o">}</span>/ssl/*.<span class="o">{</span>crt,key<span class="o">}</span>
</code></pre></div>
</li>
<li>
<p>Provision new certificates using <a href="https://tailscale.com/kb/1080/cli#cert"><code>tailscale cert</code></a>
command. Optionally you may create a directory to store the certificates.</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>mkdir<span class="w"> </span>.cert
<span class="gp">[root@pikvm ~]# </span><span class="nb">cd</span><span class="w"> </span>.cert
<span class="gp">[root@pikvm .cert]# </span>tailscale<span class="w"> </span>cert<span class="w"> </span>&lt;tailscale_hostname&gt;
</code></pre></div>
</li>
<li>
<p>Copy the certificates to nginx's and vnc's ssl directories.</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>cp<span class="w"> </span>/root/.cert/&lt;tailscale_hostname&gt;.key<span class="w"> </span>/etc/kvmd/nginx/ssl/server.key
<span class="gp">[root@pikvm ~]# </span>cp<span class="w"> </span>/root/.cert/&lt;tailscale_hostname&gt;.crt<span class="w"> </span>/etc/kvmd/nginx/ssl/server.crt
</code></pre></div>
<p>Repeat the same steps for vnc if you have configured it.</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>cp<span class="w"> </span>/root/.cert/&lt;tailscale_hostname&gt;.key<span class="w"> </span>/etc/kvmd/vnc/ssl/server.key
<span class="gp">[root@pikvm ~]# </span>cp<span class="w"> </span>/root/.cert/&lt;tailscale_hostname&gt;.crt<span class="w"> </span>/etc/kvmd/vnc/ssl/server.crt
</code></pre></div>
</li>
<li>
<p>Grant file ownership to nginx and vnc services. Switch filesystem to ReadOnly again</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>chown<span class="w"> </span>:kvmd-nginx<span class="w"> </span>/etc/kvmd/nginx/ssl/*
<span class="gp">[root@pikvm ~]# </span>chown<span class="w"> </span>:kvmd-vnc<span class="w"> </span>/etc/kvmd/vnc/ssl/*
<span class="gp">[root@pikvm ~]# </span>ro
</code></pre></div>
</li>
<li>
<p>Restart nginx and vnc services</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>restart<span class="w"> </span>kvmd-nginx
<span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>restart<span class="w"> </span>kvmd-vnc
</code></pre></div>
</li>
</ol>
<hr>
<h2 id="automated-ephemeral-tailscale-certificates-renewal">Automated Ephemeral Tailscale Certificates Renewal<a class="headerlink" href="#automated-ephemeral-tailscale-certificates-renewal" title="Permanent link"></a></h2>
<p>Tailscale has a nice option of running an HTTPS on your behalf within your tailnet: <a href="https://tailscale.com/kb/1312/serve"><code>tailscale serve</code></a>. It is using Let's Encrypt certificates and renews them every 90 days. The issue is that PiKVMs filesystem is read-only. While tailscale will diligently request new certificates, it will fail to write it on the disk and hence will try to request new certificates next time you access your web server. Let's Encrypt has a limit of 5 certificates for the server per week, so you will end up with an inoperable server and rate-limited by Let's Encrypt for a day or so.</p>
<p>Here's the command that allows you to seamlessly run HTTPS proxy for your PiKVM:
</p><div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>tailscale<span class="w"> </span>serve<span class="w"> </span>--bg<span class="w"> </span>https+insecure://localhost:443
</code></pre></div>
And if you want to stop tailscale from serving HTTPS, you can do this by running:
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>tailscale<span class="w"> </span>serve<span class="w"> </span>--https<span class="o">=</span><span class="m">443</span><span class="w"> </span>off
<span class="go">````</span>
<span class="gp">#</span><span class="c1">## Root cause</span>
<span class="go">Tailscale needs to refresh TLS certificates and write state under `/var/lib/tailscale`. </span>
<span class="go">On PiKVM, the root filesystem is read-only, so direct writes fail. </span>
<span class="go">We can fix this by mounting an **ephemeral overlay filesystem (tmpfs) in RAM** for `/var/lib/tailscale`, backed by a persistent lowerdir (`/root/tailscale-state`).</span>
<span class="go">This ensures that certificate rotation and state writes work without breaking PiKVMs read-only state.</span>
<span class="go">!!! warning</span>
<span class="go"> The **caveat** is that renewed certificates exist only in RAM. After a reboot, Tailscale falls back to the older certificates on disk, requests fresh ones, and stores them in RAM again.</span>
<span class="go"> If you reboot PiKVM too frequently, this can trigger Let's Encrypt's rate limits.</span>
<span class="gp">#</span><span class="c1">## Solution</span>
<span class="go">Core idea:</span>
<span class="go">- Mount a **tmpfs** over Tailscale's state folder stored in root's home: /root/tailscale-state. </span>
<span class="go">- Mount the resulting *merged* layer onto the actual Tailscale state folder at /var/lib/tailscale.</span>
<span class="go">- An **overlayfs** will transparently present this folder to Tailscale, while changes are kept in the RAM-based overlay layer.</span>
<span class="go">**Note**: Overlayfs requires that the upperdir and workdir exist before creating the overlay.</span>
<span class="go">Since these directories live in RAM, they disappear after every reboot.</span>
<span class="go">This means we cannot use fstab to declare the mount points.</span>
<span class="go">Instead, we implement this with a systemd service that runs a setup script during boot, before tailscaled starts.</span>
<span class="go">1. Switch filesystem to RW and copy Tailscale state:</span>
<span class="go">```console</span>
<span class="gp">[root@pikvm ~]# </span>rw
<span class="gp">[root@pikvm ~]# </span>cp<span class="w"> </span>-a<span class="w"> </span>/var/lib/tailscale<span class="w"> </span>/root/tailscale-state
<span class="go">````</span>
<span class="go">2. Create a helper script, save as `/usr/local/bin/setup-tailscale-overlay.sh`:</span>
<span class="go">```bash</span>
<span class="gp">#</span>!/bin/bash
<span class="go">set -e</span>
<span class="gp"># </span>Make<span class="w"> </span>tmpfs<span class="w"> </span><span class="k">for</span><span class="w"> </span>tailscale<span class="w"> </span>overlay
<span class="go">mkdir -p /tmp/tailscale-tmpfs</span>
<span class="go">mountpoint -q /tmp/tailscale-tmpfs || mount -t tmpfs tmpfs /tmp/tailscale-tmpfs</span>
<span class="gp"># </span>Prepare<span class="w"> </span>overlay<span class="w"> </span><span class="nb">dirs</span>
<span class="go">mkdir -p /tmp/tailscale-tmpfs/upper</span>
<span class="go">mkdir -p /tmp/tailscale-tmpfs/work</span>
<span class="go">mkdir -p /tmp/tailscale-merged</span>
<span class="gp"># </span>Mount<span class="w"> </span>overlay<span class="w"> </span><span class="o">(</span><span class="nv">lowerdir</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>persistent<span class="w"> </span><span class="nb">readonly</span><span class="w"> </span>state<span class="w"> </span><span class="k">in</span><span class="w"> </span>/root<span class="o">)</span>
<span class="go">mountpoint -q /tmp/tailscale-merged || mount -t overlay overlay \</span>
<span class="go"> -o lowerdir=/root/tailscale-state,upperdir=/tmp/tailscale-tmpfs/upper,workdir=/tmp/tailscale-tmpfs/work \</span>
<span class="go"> /tmp/tailscale-merged</span>
<span class="gp"># </span>Bind<span class="w"> </span>merged<span class="w"> </span>to<span class="w"> </span>/var/lib/tailscale
<span class="go">mountpoint -q /var/lib/tailscale &amp;&amp; umount /var/lib/tailscale || true</span>
<span class="go">mount --bind /tmp/tailscale-merged /var/lib/tailscale</span>
</code></pre></div>
<p>Make it executable:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>chmod<span class="w"> </span>+x<span class="w"> </span>/usr/local/bin/setup-tailscale-overlay.sh
</code></pre></div>
<ol>
<li>Create a systemd unit</li>
</ol>
<p>We need to run the overlay setup <strong>after <code>/tmp</code> is mounted</strong> but <strong>before <code>tailscaled.service</code></strong>.</p>
<p>Save as <code>/etc/systemd/system/tailscale-overlay.service</code>:</p>
<div class="highlight"><pre><span></span><code><span class="k">[Unit]</span>
<span class="na">Description</span><span class="o">=</span><span class="s">Setup overlayfs for Tailscale</span>
<span class="na">After</span><span class="o">=</span><span class="s">local-fs.target tmp.mount</span>
<span class="na">Before</span><span class="o">=</span><span class="s">tailscaled.service</span>
<span class="k">[Service]</span>
<span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
<span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/local/bin/setup-tailscale-overlay.sh</span>
<span class="na">RemainAfterExit</span><span class="o">=</span><span class="s">yes</span>
<span class="k">[Install]</span>
<span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</code></pre></div>
<p><strong>Notes:</strong></p>
<ul>
<li><code>local-fs.target</code> ensures all local filesystems (including <code>/tmp</code> tmpfs from fstab) are mounted.</li>
<li><code>tmp.mount</code> is added explicitly in case your system defines it.</li>
<li>
<p>Overlay is mounted and bound before <code>tailscaled</code> starts.</p>
</li>
<li>
<p>Enable and reload</p>
</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>daemon-reload
<span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>tailscale-overlay.service
<span class="gp">[root@pikvm ~]# </span>ro
</code></pre></div>
<hr>
<h3 id="boot-sequence-recap">Boot sequence recap:<a class="headerlink" href="#boot-sequence-recap" title="Permanent link"></a></h3>
<ol>
<li>tmpfs is mounted at <code>/tmp/tailscale-tmpfs</code></li>
<li><code>upper</code> + <code>work</code> dirs are recreated inside tmpfs</li>
<li>overlay is mounted with <code>/root/tailscale-state</code> as lowerdir</li>
<li>overlay bind-mounted to <code>/var/lib/tailscale</code></li>
<li><code>tailscaled.service</code> starts with writable state</li>
</ol>
<hr>
<h2 id="troubleshooting">Troubleshooting<a class="headerlink" href="#troubleshooting" title="Permanent link"></a></h2>
<ul>
<li>
<p>If something does not work, the usual advice is to completely remove Tailscale from PiKVM and perform a clean installation:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>rw
<span class="gp">[root@pikvm ~]# </span>pacman<span class="w"> </span>-Rscnd<span class="w"> </span>tailscale
<span class="gp">[root@pikvm ~]# </span>rm<span class="w"> </span>-rf<span class="w"> </span>/var/lib/tailscale<span class="w"> </span>/var/cache/tailscale
<span class="gp">[root@pikvm ~]# </span>reboot
</code></pre></div>
</li>
</ul>
<p>Now, follow the instructions from the beginning to re-install Tailscale.</p>
<ul>
<li>
<p>In case of certificate issues you can try the following steps to debug and fix.</p>
</li>
<li>
<p>Check if the services are running. If not please start them. For example,
web UI service can be checked using:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>status<span class="w"> </span>kvmd-nginx
</code></pre></div>
<p>For VNC:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>status<span class="w"> </span>kvmd-vnc
</code></pre></div>
</li>
<li>
<p>If the services are running but not accessible or showing a warning, check
the respective logs. For web UI:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>journalctl<span class="w"> </span>-xeu<span class="w"> </span>kvmd-nginx
</code></pre></div>
</li>
<li>
<p>If the logs shows TLS/certificate/permissions errors, the issue may be with
file ownership. The services must have at least the group ownership of the
certificates. The ownership should look similar to this:</p>
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>ls<span class="w"> </span>-l<span class="w"> </span>/etc/kvmd/<span class="o">{</span>nginx,vnc<span class="o">}</span>/ssl
<span class="go">/etc/kvmd/nginx/ssl:</span>
<span class="go">total 8</span>
<span class="go">-r--r--r-- 1 root kvmd-nginx 2872 Jan 3 16:07 server.crt</span>
<span class="go">-r--r----- 1 root kvmd-nginx 227 Jan 3 16:07 server.key</span>
<span class="go">/etc/kvmd/vnc/ssl:</span>
<span class="go">total 8</span>
<span class="go">-r--r--r-- 1 root kvmd-vnc 2872 Jan 3 16:07 server.crt</span>
<span class="go">-r--r----- 1 root kvmd-vnc 227 Jan 3 16:07 server.key</span>
</code></pre></div>
</li>
</ul></div>
</article>
</div>
<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"></path></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
<div class="md-copyright__highlight">
Copyright © 2018-2025 Maxim Devaev
</div>
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">{"base": "..", "features": ["navigation.indexes", "navigation.sections", "navigation.top", "navigation.expand", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "../assets/javascripts/workers/search.973d3a69.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.f55a23d4.min.js"></script>
<script src="../_assets/add_paragraphs.js"></script>
<script src="../_assets/scroll_to_summary.js"></script>
<script id="init-glightbox">const lightbox = GLightbox({"touchNavigation": false, "loop": false, "zoomable": true, "draggable": true, "openEffect": "zoom", "closeEffect": "zoom", "slideEffect": "slide"});
document$.subscribe(()=>{ lightbox.reload(); });
</script></body></html>