all: remove the silver-nitrate and httpx dependencies
- ID
7fd9cd0- date
2026-03-27 15:48:22+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
32ca58f- message
all: remove the silver-nitrate and httpx dependencies silver-nitrate is a library I wrote while working at the Flickr Foundation, but they're slowing down and it's unclear if the library will still be maintained: https://www.flickr.org/looking-ahead-simplifying-our-strategy/ I wrote all this code, so just copy it into this project. It's unclear if httpx is still maintained, so replace it with standard library. See https://tildeweb.nl/~michiel/httpxyz.html- changed files
15 files, 1187 additions, 228 deletionsdev_requirements.indev_requirements.txtpyproject.tomlsrc/chives/urls.pytests/cassettes.pytests/conftest.pytests/fixtures/cassettes/TestIsMastodonHost.test_mastodon_servers[social.jvns.ca].ymltests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[alexwlchan.net].ymltests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[example.com].ymltests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[peertube.tv].ymltests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[tailscale.com].ymltests/stubs/vcr.cassette.pyitests/stubs/vcr.pyitests/test_cassettes.pytests/test_urls.py
Changed files
dev_requirements.in (100) → dev_requirements.in (85)
diff --git a/dev_requirements.in b/dev_requirements.in
index 730f3d7..735adba 100644
--- a/dev_requirements.in
+++ b/dev_requirements.in
@@ -3,6 +3,6 @@
build
mypy
pytest-cov
+pytest-vcr
ruff
-silver-nitrate[cassettes]
twine
dev_requirements.txt (2477) → dev_requirements.txt (2259)
diff --git a/dev_requirements.txt b/dev_requirements.txt
index 2683edd..1e90bc9 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -1,45 +1,34 @@
# This file was autogenerated by uv via the following command:
-# uv pip compile dev_requirements.in --output-file dev_requirements.txt
+# uv pip compile dev_requirements.in --output-file=dev_requirements.txt --exclude-newer=P7D
-e file:.
# via -r dev_requirements.in
-anyio==4.12.1
- # via httpx
build==1.4.0
# via -r dev_requirements.in
certifi==2026.2.25
# via
- # httpcore
- # httpx
+ # alexwlchan-chives
# requests
-charset-normalizer==3.4.4
+charset-normalizer==3.4.6
# via requests
-coverage==7.13.4
+coverage==7.13.5
# via pytest-cov
docutils==0.22.4
# via readme-renderer
greenlet==3.3.2
# via playwright
-h11==0.16.0
- # via httpcore
-httpcore==1.0.9
- # via httpx
-httpx==0.28.1
- # via alexwlchan-chives
hyperlink==21.0.0
# via alexwlchan-chives
id==1.6.1
# via twine
idna==3.11
# via
- # anyio
- # httpx
# hyperlink
# requests
iniconfig==2.3.0
# via pytest
jaraco-classes==3.4.0
# via keyring
-jaraco-context==6.1.0
+jaraco-context==6.1.1
# via keyring
jaraco-functools==4.4.0
# via keyring
@@ -90,11 +79,10 @@ pytest==9.0.2
# alexwlchan-chives
# pytest-cov
# pytest-vcr
- # silver-nitrate
pytest-cov==7.0.0
# via -r dev_requirements.in
pytest-vcr==1.0.2
- # via silver-nitrate
+ # via -r dev_requirements.in
pyyaml==6.0.3
# via vcrpy
rapidfuzz==3.14.3
@@ -111,9 +99,7 @@ rfc3986==2.0.0
# via twine
rich==14.3.3
# via twine
-ruff==0.15.4
- # via -r dev_requirements.in
-silver-nitrate==1.8.1
+ruff==0.15.7
# via -r dev_requirements.in
twine==6.2.0
# via -r dev_requirements.in
@@ -128,5 +114,5 @@ urllib3==2.6.3
# twine
vcrpy==8.1.1
# via pytest-vcr
-wrapt==2.1.1
+wrapt==2.1.2
# via vcrpy
pyproject.toml (1295) → pyproject.toml (1297)
diff --git a/pyproject.toml b/pyproject.toml
index 676a669..355cb94 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,7 +26,7 @@ license = "MIT"
[project.optional-dependencies]
media = ["Pillow"]
static_site_tests = ["playwright", "pytest", "rapidfuzz"]
-urls = ["httpx", "hyperlink"]
+urls = ["certifi", "hyperlink"]
[project.urls]
"Homepage" = "https://github.com/alexwlchan/chives"
src/chives/urls.py (3526) → src/chives/urls.py (3908)
diff --git a/src/chives/urls.py b/src/chives/urls.py
index cb6021a..ecdec90 100644
--- a/src/chives/urls.py
+++ b/src/chives/urls.py
@@ -1,7 +1,15 @@
"""Code for manipulating and tidying URLs."""
+import json
from pathlib import Path
import re
+import ssl
+from ssl import SSLCertVerificationError
+import urllib.error
+from urllib.error import HTTPError
+import urllib.request
+
+import certifi
__all__ = [
@@ -43,6 +51,8 @@ def is_mastodon_host(hostname: str) -> bool:
}:
return True
+ ssl_context = ssl.create_default_context(cafile=certifi.where())
+
# See https://github.com/mastodon/mastodon/discussions/30547
#
# Fist we look at /.well-known/nodeinfo, which returns a response
@@ -57,14 +67,19 @@ def is_mastodon_host(hostname: str) -> bool:
# ]
# }
#
- import httpx
+ nodeinfo_url = f"https://{hostname}/.well-known/nodeinfo"
- nodeinfo_resp = httpx.get(f"https://{hostname}/.well-known/nodeinfo")
try:
- nodeinfo_resp.raise_for_status()
- except Exception:
+ nodeinfo_resp = urllib.request.urlopen(nodeinfo_url, context=ssl_context)
+ except HTTPError as err:
+ err.close()
+ return False
+ except SSLCertVerificationError:
return False
+ nodeinfo = json.loads(nodeinfo_resp.read())
+ nodeinfo_resp.close()
+
# Then we try to call $.links[0].href, which should return something
# like:
#
@@ -74,18 +89,16 @@ def is_mastodon_host(hostname: str) -> bool:
# …
#
try:
- href = nodeinfo_resp.json()["links"][0]["href"]
+ link_href = nodeinfo["links"][0]["href"]
except (KeyError, IndexError): # pragma: no cover
return False
- link_resp = httpx.get(href)
- try:
- link_resp.raise_for_status()
- except Exception: # pragma: no cover
- return False
+ link_resp = urllib.request.urlopen(link_href, context=ssl_context)
+ link_info = json.loads(link_resp.read())
+ link_resp.close()
try:
- return bool(link_resp.json()["software"]["name"] == "mastodon")
+ return bool(link_info["software"]["name"] == "mastodon")
except (KeyError, IndexError): # pragma: no cover
return False
tests/cassettes.py (0) → tests/cassettes.py (2213)
diff --git a/tests/cassettes.py b/tests/cassettes.py
new file mode 100644
index 0000000..b568dd8
--- /dev/null
+++ b/tests/cassettes.py
@@ -0,0 +1,77 @@
+"""
+pytest fixtures for working with vcrpy to record HTTP requests.
+
+This allows us to record HTTP interactions as YAML files, so they can
+be "played back" later -- e.g. in automated tests or GitHub Actions.
+This means our tests are working with real responses, but don't
+depend on the original service being up and running.
+
+This establishes a couple of conventions for where cassettes are stored
+and how they're named.
+
+See https://vcrpy.readthedocs.io/
+"""
+
+from collections.abc import Iterator
+
+import pytest
+import vcr
+from vcr.cassette import Cassette
+
+
+__all__ = ["cassette_name", "vcr_cassette"]
+
+
+def get_cassette_name(request: pytest.FixtureRequest) -> str:
+ """
+ Returns the name of a cassette for vcr.py.
+
+ The name can be made up of (up to) three parts:
+
+ - the name of the test class
+ - the name of the test function
+ - the ID of the test case in @pytest.mark.parametrize
+
+ """
+ name = request.node.name
+
+ # This is to catch cases where e.g. we try to include a complete
+ # HTTP URL in a cassette name, which creates very messy folders in
+ # the fixtures directory.
+ if any(char in name for char in ":/"):
+ raise ValueError(
+ "Illegal characters in VCR cassette name - "
+ "please set a test ID with pytest.param(…, id='…')"
+ )
+
+ if request.cls is not None:
+ return f"{request.cls.__name__}.{name}.yml"
+ else:
+ return f"{name}.yml"
+
+
+@pytest.fixture
+def cassette_name(request: pytest.FixtureRequest) -> str:
+ """
+ Returns the filename of a VCR cassette to use in tests.
+
+ This is useful when you need some custom vcr.py options, and
+ can't use the prebuilt `vcr_cassette` fixture.
+ """
+ return get_cassette_name(request)
+
+
+@pytest.fixture
+def vcr_cassette(cassette_name: str) -> Iterator[Cassette]:
+ """
+ Creates a VCR cassette for use in tests.
+
+ Tests will record their HTTP interactions as "cassettes" using vcr.py,
+ which can be replayed offline (e.g. in CI tests).
+ """
+ with vcr.use_cassette(
+ cassette_name,
+ cassette_library_dir="tests/fixtures/cassettes",
+ decode_compressed_response=True,
+ ) as cassette:
+ yield cassette
tests/conftest.py (173) → tests/conftest.py (165)
diff --git a/tests/conftest.py b/tests/conftest.py
index 9a07b84..993aeab 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,6 +1,6 @@
"""Shared helpers and test fixtures."""
-from nitrate.cassettes import cassette_name, vcr_cassette
+from cassettes import cassette_name, vcr_cassette
pytest_plugins = "pytester"
tests/fixtures/cassettes/TestIsMastodonHost.test_mastodon_servers[social.jvns.ca].yml (4230) → tests/fixtures/cassettes/TestIsMastodonHost.test_mastodon_servers[social.jvns.ca].yml (4418)
diff --git a/tests/fixtures/cassettes/TestIsMastodonHost.test_mastodon_servers[social.jvns.ca].yml b/tests/fixtures/cassettes/TestIsMastodonHost.test_mastodon_servers[social.jvns.ca].yml
index f240d7c..14ba026 100644
--- a/tests/fixtures/cassettes/TestIsMastodonHost.test_mastodon_servers[social.jvns.ca].yml
+++ b/tests/fixtures/cassettes/TestIsMastodonHost.test_mastodon_servers[social.jvns.ca].yml
@@ -1,17 +1,13 @@
interactions:
- request:
- body: ''
+ body: null
headers:
- Accept:
- - '*/*'
- Accept-Encoding:
- - gzip, deflate
Connection:
- - keep-alive
+ - close
Host:
- social.jvns.ca
User-Agent:
- - python-httpx/0.28.1
+ - Python-urllib/3.14
method: GET
uri: https://social.jvns.ca/.well-known/nodeinfo
response:
@@ -19,19 +15,21 @@ interactions:
string: '{"links":[{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0","href":"https://social.jvns.ca/nodeinfo/2.0"}]}'
headers:
Connection:
- - keep-alive
+ - close
+ Content-Length:
+ - '114'
Content-Type:
- application/json; charset=utf-8
Date:
- - Thu, 04 Dec 2025 12:14:49 GMT
+ - Sat, 28 Mar 2026 11:28:19 GMT
Strict-Transport-Security:
+ - max-age=63072000; includeSubDomains
- max-age=31536000
- Transfer-Encoding:
- - chunked
+ Vary:
+ - Accept-Encoding
+ - Origin
cache-control:
- max-age=259200, public
- content-length:
- - '114'
content-security-policy:
- 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src
''self'' https://social.jvns.ca; img-src ''self'' data: blob: https://social.jvns.ca
@@ -40,61 +38,63 @@ interactions:
''self'' blob: https://social.jvns.ca; worker-src ''self'' blob: https://social.jvns.ca;
connect-src ''self'' data: blob: https://social.jvns.ca https://cdn.masto.host
wss://social.jvns.ca; script-src ''self'' https://social.jvns.ca ''wasm-unsafe-eval'';
- frame-src ''self'' https:; style-src ''self'' https://social.jvns.ca ''nonce-cYXJpX/juTVw0Sc+MAA7BQ=='''
+ frame-src ''self'' https:; style-src ''self'' https://social.jvns.ca ''nonce-SLG/a2lOZjjg0yVI2h0UFQ=='''
etag:
- W/"41981c7ccfa1674c1535b6eea835d7e5"
referrer-policy:
- same-origin
server:
- Mastodon
+ strict-transport-security:
+ - max-age=63072000; includeSubDomains
+ - max-age=31536000
vary:
+ - Accept-Encoding
- Origin
x-content-type-options:
- nosniff
x-frame-options:
- DENY
x-request-id:
- - d4888141-9cb3-4305-b5dd-a8b0842a2d05
+ - 7492826e-5911-4ac8-bd7c-1ef18fb5a3c1
x-runtime:
- - '0.003398'
+ - '0.001392'
x-xss-protection:
- '0'
status:
code: 200
message: OK
- request:
- body: ''
+ body: null
headers:
- Accept:
- - '*/*'
- Accept-Encoding:
- - gzip, deflate
Connection:
- - keep-alive
+ - close
Host:
- social.jvns.ca
User-Agent:
- - python-httpx/0.28.1
+ - Python-urllib/3.14
method: GET
uri: https://social.jvns.ca/nodeinfo/2.0
response:
body:
- string: '{"version":"2.0","software":{"name":"mastodon","version":"4.5.2"},"protocols":["activitypub"],"services":{"outbound":[],"inbound":[]},"usage":{"users":{"total":4,"activeMonth":4,"activeHalfyear":4},"localPosts":7409},"openRegistrations":false,"metadata":{"nodeName":"Mastodon","nodeDescription":""}}'
+ string: '{"version":"2.0","software":{"name":"mastodon","version":"4.5.8"},"protocols":["activitypub"],"services":{"outbound":[],"inbound":[]},"usage":{"users":{"total":4,"activeMonth":4,"activeHalfyear":4},"localPosts":7915},"openRegistrations":false,"metadata":{"nodeName":"Mastodon","nodeDescription":""}}'
headers:
Connection:
- - keep-alive
+ - close
+ Content-Length:
+ - '299'
Content-Type:
- application/json; charset=utf-8
Date:
- - Thu, 04 Dec 2025 12:14:49 GMT
+ - Sat, 28 Mar 2026 11:28:19 GMT
Strict-Transport-Security:
+ - max-age=63072000; includeSubDomains
- max-age=31536000
- Transfer-Encoding:
- - chunked
+ Vary:
+ - Accept-Encoding
+ - Origin
cache-control:
- max-age=1800, public
- content-length:
- - '299'
content-security-policy:
- 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src
''self'' https://social.jvns.ca; img-src ''self'' data: blob: https://social.jvns.ca
@@ -103,23 +103,27 @@ interactions:
''self'' blob: https://social.jvns.ca; worker-src ''self'' blob: https://social.jvns.ca;
connect-src ''self'' data: blob: https://social.jvns.ca https://cdn.masto.host
wss://social.jvns.ca; script-src ''self'' https://social.jvns.ca ''wasm-unsafe-eval'';
- frame-src ''self'' https:; style-src ''self'' https://social.jvns.ca ''nonce-KudwCpfFUyr8bIzc9hLCuA=='''
+ frame-src ''self'' https:; style-src ''self'' https://social.jvns.ca ''nonce-uvtrA197UKg93d3cEYFepw=='''
etag:
- - W/"def238b77fc5db88a115321ee60e49e7"
+ - W/"e5770b11e9504692808d5c4bbb2a1741"
referrer-policy:
- same-origin
server:
- Mastodon
+ strict-transport-security:
+ - max-age=63072000; includeSubDomains
+ - max-age=31536000
vary:
- - Accept, Origin
+ - Accept-Encoding
+ - Origin
x-content-type-options:
- nosniff
x-frame-options:
- DENY
x-request-id:
- - 3ef92cee-53b7-41e1-b1f1-5b9b094c5616
+ - 0161e669-c01b-4ad0-b976-78c6185f6157
x-runtime:
- - '0.008559'
+ - '0.001917'
x-xss-protection:
- '0'
status:
tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[alexwlchan.net].yml (1406) → tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[alexwlchan.net].yml (0)
diff --git a/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[alexwlchan.net].yml b/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[alexwlchan.net].yml
deleted file mode 100644
index 6b8e4c1..0000000
--- a/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[alexwlchan.net].yml
+++ /dev/null
@@ -1,51 +0,0 @@
-interactions:
-- request:
- body: ''
- headers:
- Accept:
- - '*/*'
- Accept-Encoding:
- - gzip, deflate
- Connection:
- - keep-alive
- Host:
- - alexwlchan.net
- User-Agent:
- - python-httpx/0.28.1
- method: GET
- uri: https://alexwlchan.net/.well-known/nodeinfo
- response:
- body:
- string: ''
- headers:
- Alt-Svc:
- - h3=":443"; ma=2592000
- Content-Length:
- - '0'
- Content-Security-Policy:
- - 'default-src ''self'' ''unsafe-inline'' https://youtube-nocookie.com https://www.youtube-nocookie.com;
- script-src ''self'' ''unsafe-inline''; connect-src https://analytics.alexwlchan.net;
- img-src ''self'' ''unsafe-inline'' data:'
- Date:
- - Thu, 04 Dec 2025 12:15:34 GMT
- Location:
- - https://social.alexwlchan.net/.well-known/nodeinfo
- Permissions-Policy:
- - geolocation=(), midi=(), notifications=(), push=(), sync-xhr=(), microphone=(),
- camera=(), magnetometer=(), gyroscope=(), vibrate=(), payment=()
- Referrer-Policy:
- - no-referrer-when-downgrade
- Server:
- - Caddy
- Strict-Transport-Security:
- - max-age=31536000; includeSubDomains
- X-Content-Type-Options:
- - nosniff
- X-Frame-Options:
- - ALLOWALL
- X-Xss-Protection:
- - 1; mode=block
- status:
- code: 301
- message: Moved Permanently
-version: 1
tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[example.com].yml (1538) → tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[example.com].yml (0)
diff --git a/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[example.com].yml b/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[example.com].yml
deleted file mode 100644
index 4a0f6f1..0000000
--- a/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[example.com].yml
+++ /dev/null
@@ -1,55 +0,0 @@
-interactions:
-- request:
- body: ''
- headers:
- Accept:
- - '*/*'
- Accept-Encoding:
- - gzip, deflate
- Connection:
- - keep-alive
- Host:
- - example.com
- User-Agent:
- - python-httpx/0.28.1
- method: GET
- uri: https://example.com/.well-known/nodeinfo
- response:
- body:
- string: '<!doctype html><html lang="en"><head><title>Example Domain</title><meta
- name="viewport" content="width=device-width, initial-scale=1"><style>body{background:#eee;width:60vw;margin:15vh
- auto;font-family:system-ui,sans-serif}h1{font-size:1.5em}div{opacity:0.8}a:link,a:visited{color:#348}</style><body><div><h1>Example
- Domain</h1><p>This domain is for use in documentation examples without needing
- permission. Avoid use in operations.<p><a href="https://iana.org/domains/example">Learn
- more</a></div></body></html>
-
- '
- headers:
- Accept-Ranges:
- - bytes
- Alt-Svc:
- - h3=":443"; ma=93600
- Cache-Control:
- - max-age=0, no-cache, no-store
- Connection:
- - keep-alive
- Content-Length:
- - '513'
- Content-Type:
- - text/html
- Date:
- - Thu, 04 Dec 2025 12:15:34 GMT
- ETag:
- - '"bc2473a18e003bdb249eba5ce893033f:1760028122.592274"'
- Expires:
- - Thu, 04 Dec 2025 12:15:34 GMT
- Last-Modified:
- - Thu, 09 Oct 2025 16:42:02 GMT
- Pragma:
- - no-cache
- Server:
- - AkamaiNetStorage
- status:
- code: 404
- message: Not Found
-version: 1
tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[peertube.tv].yml (7608) → tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[peertube.tv].yml (5130)
diff --git a/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[peertube.tv].yml b/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[peertube.tv].yml
index 75d1597..e1689b7 100644
--- a/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[peertube.tv].yml
+++ b/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[peertube.tv].yml
@@ -1,102 +1,99 @@
interactions:
- request:
- body: ''
+ body: null
headers:
- Accept:
- - '*/*'
- Accept-Encoding:
- - gzip, deflate
Connection:
- - keep-alive
+ - close
Host:
- peertube.tv
User-Agent:
- - python-httpx/0.28.1
+ - Python-urllib/3.14
method: GET
uri: https://peertube.tv/.well-known/nodeinfo
response:
body:
- string: '{"links":[{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0","href":"https://peertube.tv/nodeinfo/2.0.json"}]}'
+ string: '{"links":[{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0","href":"https://peertube.tv/nodeinfo/2.0.json"},{"rel":"https://www.w3.org/ns/activitystreams#Application","href":"https://peertube.tv/accounts/peertube"}]}'
headers:
+ Access-Control-Allow-Origin:
+ - '*'
Connection:
- - keep-alive
+ - close
Content-Length:
- - '116'
+ - '223'
Content-Type:
- application/json; charset=utf-8
Date:
- - Thu, 04 Dec 2025 12:17:46 GMT
+ - Sat, 28 Mar 2026 11:28:20 GMT
+ ETag:
+ - W/"df-TklFQ/9IV5DIROi4BSS0TgZ5/L4"
Server:
- - nginx/1.18.0 (Ubuntu)
- access-control-allow-origin:
- - '*'
- cache-control:
- - max-age=548
- etag:
- - W/"74-uYd/TxZEF87Urak29pxyd08PwVE"
- tk:
+ - nginx
+ Strict-Transport-Security:
+ - max-age=63072000; includeSubDomains
+ Tk:
- N
- x-frame-options:
+ X-Frame-Options:
- DENY
+ X-RateLimit-Limit:
+ - '200'
+ X-RateLimit-Remaining:
+ - '199'
+ X-RateLimit-Reset:
+ - '1774697311'
+ cache-control:
+ - max-age=600
x-powered-by:
- PeerTube
status:
code: 200
message: OK
- request:
- body: ''
+ body: null
headers:
- Accept:
- - '*/*'
- Accept-Encoding:
- - gzip, deflate
Connection:
- - keep-alive
+ - close
Host:
- peertube.tv
User-Agent:
- - python-httpx/0.28.1
+ - Python-urllib/3.14
method: GET
uri: https://peertube.tv/nodeinfo/2.0.json
response:
body:
- string: '{"version":"2.0","software":{"name":"peertube","version":"5.2.0"},"protocols":["activitypub"],"services":{"inbound":[],"outbound":["atom1.0","rss2.0"]},"openRegistrations":false,"usage":{"users":{"total":609,"activeMonth":8,"activeHalfyear":35},"localPosts":18598,"localComments":93},"metadata":{"taxonomy":{"postsName":"Videos"},"nodeName":"PeerTube.TV","nodeDescription":"Videos
- sharing & live streaming on free open source software PeerTube! No ads, no
- tracking, no spam.","nodeConfig":{"search":{"remoteUri":{"users":true,"anonymous":false}},"plugin":{"registered":[{"npmName":"peertube-plugin-upload-instructions","name":"upload-instructions","version":"0.1.1","description":"Show
- an instructions modal right before uploading","clientScripts":{"dist/common-client-plugin.js":{"script":"dist/common-client-plugin.js","scopes":["common"]}}},{"npmName":"peertube-plugin-custom-links","name":"custom-links","version":"0.0.10","description":"PeerTube
- plugin that allows you to add custom links on the bottom of the menu","clientScripts":{"dist/common-client-plugin.js":{"script":"dist/common-client-plugin.js","scopes":["common"]}}},{"npmName":"peertube-plugin-glavliiit","name":"glavliiit","version":"0.0.10","description":"Enhanced
- moderation tool for PeerTube","clientScripts":{}},{"npmName":"peertube-plugin-categories","name":"categories","version":"1.2.7","description":"Manage
- video categories.","clientScripts":{"src/client/admin-plugin-settings.js":{"script":"src/client/admin-plugin-settings.js","scopes":["admin-plugin"]}}},{"npmName":"peertube-plugin-creative-commons","name":"creative-commons","version":"1.2.0","description":"Standardized
+ string: '{"version":"2.0","software":{"name":"peertube","version":"8.1.3"},"protocols":["activitypub"],"services":{"inbound":[],"outbound":["atom1.0","rss2.0"]},"openRegistrations":false,"usage":{"users":{"total":609,"activeMonth":10,"activeHalfyear":35},"localPosts":688,"localComments":86},"metadata":{"taxonomy":{"postsName":"Videos"},"nodeName":"PeerTube.TV","nodeDescription":"Videos
+ sharing & live streaming on free open source software PeerTube!","nodeConfig":{"search":{"remoteUri":{"users":true,"anonymous":false}},"plugin":{"registered":[{"npmName":"peertube-plugin-creative-commons","name":"creative-commons","version":"1.2.0","description":"Standardized
display of Creative Commons licenses. Uses short identifiers like CC BY-SA
- 4.0 instead of descriptive text.","clientScripts":{"client/video-watch-client-plugin.js":{"script":"client/video-watch-client-plugin.js","scopes":["video-watch"]}}},{"npmName":"peertube-plugin-social-sharing-rus","name":"social-sharing-rus","version":"0.11.0","description":"Share
- a video or playlist URL on social media (Mastodon, WordPress, reddit, Twitter,
- etc.)","clientScripts":{"dist/common-client-plugin.js":{"script":"dist/common-client-plugin.js","scopes":["common"]}}},{"npmName":"peertube-plugin-menu-items","name":"menu-items","version":"0.0.4","description":"PeerTube
- plugin menu-items","clientScripts":{"dist/common-client-plugin.js":{"script":"dist/common-client-plugin.js","scopes":["common"]}}},{"npmName":"peertube-plugin-chapters","name":"chapters","version":"1.1.3","description":"PeerTube
- chapter plugin","clientScripts":{"dist/client/video-watch-client-plugin.js":{"script":"dist/client/video-watch-client-plugin.js","scopes":["video-watch","embed"]},"dist/client/video-edit-client-plugin.js":{"script":"dist/client/video-edit-client-plugin.js","scopes":["video-edit"]}}},{"npmName":"peertube-plugin-simplelogo","name":"simplelogo","version":"0.0.5","description":"Plugin
- that let you change logo and favicon on your PeerTube instance.","clientScripts":{"client/common-client-plugin.js":{"script":"client/common-client-plugin.js","scopes":["common"]}}},{"npmName":"peertube-plugin-video-annotation","name":"video-annotation","version":"0.0.7","description":"PeerTube
- plugin video annotation","clientScripts":{"dist/embed-client-plugin.js":{"script":"dist/embed-client-plugin.js","scopes":["embed"]},"dist/video-edit-client-plugin.js":{"script":"dist/video-edit-client-plugin.js","scopes":["video-edit"]},"dist/video-watch-client-plugin.js":{"script":"dist/video-watch-client-plugin.js","scopes":["video-watch"]}}},{"npmName":"peertube-plugin-livechat","name":"livechat","version":"7.2.1","description":"PeerTube
- plugin livechat: offers a way to embed a chat system into Peertube.","clientScripts":{"dist/client/videowatch-client-plugin.js":{"script":"dist/client/videowatch-client-plugin.js","scopes":["video-watch"]},"dist/client/common-client-plugin.js":{"script":"dist/client/common-client-plugin.js","scopes":["common"]},"dist/client/admin-plugin-client-plugin.js":{"script":"dist/client/admin-plugin-client-plugin.js","scopes":["admin-plugin"]}}}]},"theme":{"registered":[{"npmName":"peertube-theme-dark-evolution","name":"dark-evolution","version":"1.0.4","description":"Evolution
- of the official PeerTube dark theme","css":["assets/style.css"],"clientScripts":{}},{"npmName":"peertube-theme-dark","name":"dark","version":"2.5.0","description":"PeerTube
- dark theme","css":["assets/style.css"],"clientScripts":{}}],"default":"dark-evolution"},"email":{"enabled":true},"contactForm":{"enabled":true},"transcoding":{"hls":{"enabled":true},"webtorrent":{"enabled":true},"enabledResolutions":[144,240,360,480,720,1080]},"live":{"enabled":true,"transcoding":{"enabled":true,"enabledResolutions":[144,480,720,1080]}},"import":{"videos":{"http":{"enabled":true},"torrent":{"enabled":false}}},"autoBlacklist":{"videos":{"ofUsers":{"enabled":false}}},"avatar":{"file":{"size":{"max":4194304},"extensions":[".png",".jpeg",".jpg",".gif",".webp"]}},"video":{"image":{"extensions":[".png",".jpg",".jpeg",".webp"],"size":{"max":4194304}},"file":{"extensions":[".webm",".ogv",".ogg",".mp4",".mkv",".mov",".qt",".mqv",".m4v",".flv",".f4v",".wmv",".avi",".3gp",".3gpp",".3g2",".3gpp2",".nut",".mts",".m2ts",".mpv",".m2v",".m1v",".mpg",".mpe",".mpeg",".vob",".mxf",".mp3",".wma",".wav",".flac",".aac",".m4a",".ac3"]}},"videoCaption":{"file":{"size":{"max":20971520},"extensions":[".vtt",".srt"]}},"user":{"videoQuota":53687091200,"videoQuotaDaily":5368709120},"trending":{"videos":{"intervalDays":7}},"tracker":{"enabled":true}}}}'
+ 4.0 instead of descriptive text.","clientScripts":{"client/video-watch-client-plugin.js":{"script":"client/video-watch-client-plugin.js","scopes":["video-watch"]}}},{"npmName":"peertube-plugin-categories","name":"categories","version":"1.2.7","description":"Manage
+ video categories.","clientScripts":{"src/client/admin-plugin-settings.js":{"script":"src/client/admin-plugin-settings.js","scopes":["admin-plugin"]}}},{"npmName":"peertube-plugin-video-annotation","name":"video-annotation","version":"0.2.0","description":"PeerTube
+ plugin video annotation","clientScripts":{"dist/embed-client-plugin.js":{"script":"dist/embed-client-plugin.js","scopes":["embed"]},"dist/video-edit-client-plugin.js":{"script":"dist/video-edit-client-plugin.js","scopes":["video-edit"]},"dist/video-watch-client-plugin.js":{"script":"dist/video-watch-client-plugin.js","scopes":["video-watch"]}}}]},"theme":{"registered":[{"npmName":"peertube-theme-dark","name":"dark","version":"3.0.3","description":"PeerTube
+ dark theme","css":["assets/style.css"],"clientScripts":{}}],"default":"default"},"email":{"enabled":true},"contactForm":{"enabled":true},"transcoding":{"hls":{"enabled":true},"web_videos":{"enabled":true},"enabledResolutions":[360]},"live":{"enabled":false,"transcoding":{"enabled":true,"enabledResolutions":[144,480,720,1080]}},"import":{"videos":{"http":{"enabled":true},"torrent":{"enabled":false}}},"autoBlacklist":{"videos":{"ofUsers":{"enabled":false}}},"avatar":{"file":{"size":{"max":8388608},"extensions":[".png",".jpeg",".jpg",".gif",".webp"]}},"video":{"image":{"extensions":[".png",".jpg",".jpeg",".webp"],"size":{"max":8388608}},"file":{"extensions":[".webm",".ogv",".ogg",".mp4",".mkv",".mov",".qt",".mqv",".m4v",".flv",".f4v",".wmv",".avi",".3gp",".3gpp",".3g2",".3gpp2",".nut",".mts",".ts",".m2ts",".mpv",".m2v",".m1v",".mpg",".mpe",".mpeg",".vob",".mxf",".mp3",".wma",".wav",".flac",".aac",".m4a",".ac3"]}},"videoCaption":{"file":{"size":{"max":20971520},"extensions":[".vtt",".srt"]}},"user":{"videoQuota":53687091200,"videoQuotaDaily":5368709120},"trending":{"videos":{"intervalDays":7}},"tracker":{"enabled":true}}}}'
headers:
Access-Control-Allow-Origin:
- '*'
Connection:
- - keep-alive
+ - close
Content-Length:
- - '5567'
+ - '2859'
Content-Type:
- application/json; charset=utf-8; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"
Date:
- - Thu, 04 Dec 2025 12:17:47 GMT
+ - Sat, 28 Mar 2026 11:28:20 GMT
ETag:
- - W/"15bf-UHcLfIV97HliD7E2eKuWJsf3iEQ"
+ - W/"b2b-g18XMBMf5sGTfPJOaHXRzYFpIwA"
Server:
- - nginx/1.18.0 (Ubuntu)
+ - nginx
+ Strict-Transport-Security:
+ - max-age=63072000; includeSubDomains
Tk:
- N
X-Frame-Options:
- DENY
+ X-RateLimit-Limit:
+ - '50'
+ X-RateLimit-Remaining:
+ - '49'
+ X-RateLimit-Reset:
+ - '1774697311'
cache-control:
- max-age=600
x-powered-by:
tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[tailscale.com].yml (0) → tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[tailscale.com].yml (92014)
diff --git a/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[tailscale.com].yml b/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[tailscale.com].yml
new file mode 100644
index 0000000..fb40254
--- /dev/null
+++ b/tests/fixtures/cassettes/TestIsMastodonHost.test_non_mastodon_servers[tailscale.com].yml
@@ -0,0 +1,876 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Connection:
+ - close
+ Host:
+ - tailscale.com
+ User-Agent:
+ - Python-urllib/3.14
+ method: GET
+ uri: https://tailscale.com/.well-known/nodeinfo
+ response:
+ body:
+ string: "<!DOCTYPE html><html><head><meta charSet=\"utf-8\"/><title>404</title><meta
+ name=\"robots\" content=\"index,follow\"/><meta name=\"description\" content=\"Securely
+ connect to anything on the internet with Tailscale. Built on WireGuard\xAE\uFE0F,
+ Tailscale enables you to make finely configurable connections, secured end-to-end
+ according to zero trust principles, between any resources on any infrastructure.\"/><meta
+ property=\"og:title\" content=\"404\"/><meta property=\"og:description\" content=\"Securely
+ connect to anything on the internet with Tailscale. Built on WireGuard\xAE\uFE0F,
+ Tailscale enables you to make finely configurable connections, secured end-to-end
+ according to zero trust principles, between any resources on any infrastructure.\"/><meta
+ property=\"og:url\" content=\"https://tailscale.com/404\"/><meta property=\"og:image\"
+ content=\"https://tailscale.com/api/og-image?id=747a1d74-fd7c-43c3-83f5-ae55218823dd\"/><link
+ rel=\"canonical\" href=\"https://tailscale.com/404\"/><link rel=\"alternate\"
+ type=\"application/rss+xml\" href=\"https://tailscale.com/blog/index.xml\"/><link
+ rel=\"alternate\" type=\"application/rss+xml\" href=\"https://tailscale.com/changelog/index.xml\"/><link
+ rel=\"alternate\" type=\"application/rss+xml\" href=\"https://tailscale.com/security-bulletins/index.xml\"/><meta
+ name=\"viewport\" content=\"initial-scale=1.0, width=device-width, maximum-scale=1\"/><link
+ rel=\"icon\" href=\"/favicon.png\" type=\"image/png\"/><link rel=\"icon\"
+ href=\"/favicon.svg\" type=\"image/svg+xml\"/><meta name=\"msapplication-TileColor\"
+ content=\"#492847\"/><meta name=\"theme-color\" content=\"#ffffff\"/><script
+ async=\"\" src=\"https://bwa.marketplace.awsstatic.com/assets/partner.js\"></script><meta
+ name=\"twitter:card\" content=\"summary_large_image\"/><meta name=\"twitter:title\"
+ content=\"404\"/><meta name=\"twitter:image\" content=\"https://tailscale.com/api/og-image?id=747a1d74-fd7c-43c3-83f5-ae55218823dd\"/><meta
+ name=\"twitter:description\" content=\"Securely connect to anything on the
+ internet with Tailscale. Built on WireGuard\xAE\uFE0F, Tailscale enables you
+ to make finely configurable connections, secured end-to-end according to zero
+ trust principles, between any resources on any infrastructure.\"/><meta name=\"twitter:site\"
+ content=\"@tailscale\"/><meta name=\"next-head-count\" content=\"23\"/><link
+ rel=\"preload\" href=\"/_next/static/media/97a52bce187043ec-s.p.woff2\" as=\"font\"
+ type=\"font/woff2\" crossorigin=\"anonymous\" data-next-font=\"size-adjust\"/><link
+ rel=\"preload\" href=\"/_next/static/media/e4af272ccee01ff0-s.p.woff2\" as=\"font\"
+ type=\"font/woff2\" crossorigin=\"anonymous\" data-next-font=\"size-adjust\"/><link
+ rel=\"preload\" href=\"/_next/static/css/d9b50c7edadb4b9c.css\" as=\"style\"/><link
+ rel=\"stylesheet\" href=\"/_next/static/css/d9b50c7edadb4b9c.css\" data-n-g=\"\"/><link
+ rel=\"preload\" href=\"/_next/static/css/9eee414ec9ffd447.css\" as=\"style\"/><link
+ rel=\"stylesheet\" href=\"/_next/static/css/9eee414ec9ffd447.css\" data-n-p=\"\"/><noscript
+ data-n-css=\"\"></noscript><script defer=\"\" nomodule=\"\" src=\"/_next/static/chunks/polyfills-42372ed130431b0a.js\"></script><script
+ src=\"/_next/static/chunks/webpack-7531eb7e1d335cd8.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/framework-2f437cbb805415a5.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/main-0d17d82c40603f51.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/pages/_app-f80bd9fe7679a6c7.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/942-08460e3e449dc5c5.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/2962-c70fc1b17df9e34c.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/1604-c7588012aea9ac3b.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/895-f79a93f5c02a5693.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/8148-65f6cc3bf9da352f.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/2099-6f8261a6895d4a7e.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/2737-f9cf8578e879d066.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/6772-4a704c5f2497e9ae.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/5525-ae3805a05aa422a2.js\" defer=\"\"></script><script
+ src=\"/_next/static/chunks/pages/404-60de5b8794546a8b.js\" defer=\"\"></script><script
+ src=\"/_next/static/yXNXD6wKMziT90Wz_CAoa/_buildManifest.js\" defer=\"\"></script><script
+ src=\"/_next/static/yXNXD6wKMziT90Wz_CAoa/_ssgManifest.js\" defer=\"\"></script><style
+ id=\"__jsx-1394014874\">:root{--font-inter:'__Inter_e10f34', '__Inter_Fallback_e10f34';--font-mdio:'__MDIOFont_8d6c39',
+ '__MDIOFont_Fallback_8d6c39'}</style></head><body><div id=\"__next\"><!--$--><!--/$--><div
+ class=\"sticky top-0 z-[20000]\"><div class=\"z-[20000] w-full p-4 font-mdio
+ text-xs uppercase !tracking-[0.6px] xl-small:p-1.5 bg-gray-700 text-white\"><div
+ class=\"mx-auto flex w-full max-w-[1360px] items-center justify-center px-6
+ xl-small:max-w-[1440px] xl-small:justify-between xl-small:px-16\"><div><a
+ class=\"group flex flex-wrap items-center justify-center gap-x-2 text-pretty
+ pt-0.5 text-center font-medium text-white sm:text-left\" href=\"https://tailscale.com/blog/aperture-self-serve/?utm_source=announcement-bar&utm_medium=content&utm_campaign=aperture-self-serve\">APERTURE
+ IS NOW AVAILABLE - START BUILDING WITH AI SAFELY IN MINUTES, NO WAITLIST.<span
+ class=\"pl-2 text-gray-0/80 underline group-hover:no-underline\">Read more
+ \u2192</span></a></div><nav class=\"hidden xl-small:block\"><ul class=\"flex
+ gap-6\"><li><a class=\"block py-3.5 font-medium text-gray-0/80 hover:underline\"
+ href=\"/blog\">Blog</a></li><li><a class=\"block py-3.5 font-medium text-gray-0/80
+ hover:underline\" href=\"/docs\">Docs</a></li><li><a class=\"block py-3.5
+ font-medium text-gray-0/80 hover:underline\" href=\"/download\">Download</a></li><li><a
+ class=\"block py-3.5 font-medium text-gray-0/80 hover:underline\" href=\"/contact/sales\">Contact
+ Sales</a></li></ul></nav></div></div><header class=\"bg-white transition-colors
+ duration-300\"><div class=\"mx-auto flex h-[75px] w-full max-w-[1360px] items-center
+ justify-between gap-[4px] px-6 md:max-w-[1440px] lg:px-16\"><a href=\"/\"><svg
+ width=\"145\" height=\"28\" viewBox=\"0 0 145 28\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path
+ d=\"M3.5 17.5C5.43299 17.5 6.99999 15.933 6.99999 14C6.99999 12.067 5.43299
+ 10.5 3.5 10.5C1.567 10.5 0 12.067 0 14C0 15.933 1.567 17.5 3.5 17.5Z\" fill=\"#232222\"></path><path
+ d=\"M14 17.5C15.933 17.5 17.5 15.933 17.5 14C17.5 12.067 15.933 10.5 14 10.5C12.067
+ 10.5 10.5 12.067 10.5 14C10.5 15.933 12.067 17.5 14 17.5Z\" fill=\"#232222\"></path><path
+ d=\"M14 28C15.933 28 17.5 26.433 17.5 24.5C17.5 22.567 15.933 21 14 21C12.067
+ 21 10.5 22.567 10.5 24.5C10.5 26.433 12.067 28 14 28Z\" fill=\"#232222\"></path><path
+ d=\"M24.5 17.5C26.433 17.5 28 15.933 28 14C28 12.067 26.433 10.5 24.5 10.5C22.567
+ 10.5 21 12.067 21 14C21 15.933 22.567 17.5 24.5 17.5Z\" fill=\"#232222\"></path><g
+ opacity=\"0.4\"><path d=\"M3.5 28C5.43299 28 6.99999 26.433 6.99999 24.5C6.99999
+ 22.567 5.43299 21 3.5 21C1.567 21 0 22.567 0 24.5C0 26.433 1.567 28 3.5 28Z\"
+ fill=\"#232222\"></path><path d=\"M24.5 28C26.433 28 28 26.433 28 24.5C28
+ 22.567 26.433 21 24.5 21C22.567 21 21 22.567 21 24.5C21 26.433 22.567 28 24.5
+ 28Z\" fill=\"#232222\"></path><path d=\"M3.5 6.99999C5.43299 6.99999 6.99999
+ 5.43299 6.99999 3.5C6.99999 1.567 5.43299 0 3.5 0C1.567 0 0 1.567 0 3.5C0
+ 5.43299 1.567 6.99999 3.5 6.99999Z\" fill=\"#232222\"></path><path d=\"M14
+ 6.99999C15.933 6.99999 17.5 5.43299 17.5 3.5C17.5 1.567 15.933 0 14 0C12.067
+ 0 10.5 1.567 10.5 3.5C10.5 5.43299 12.067 6.99999 14 6.99999Z\" fill=\"#232222\"></path><path
+ d=\"M24.5 6.99999C26.433 6.99999 28 5.43299 28 3.5C28 1.567 26.433 0 24.5
+ 0C22.567 0 21 1.567 21 3.5C21 5.43299 22.567 6.99999 24.5 6.99999Z\" fill=\"#232222\"></path></g><path
+ d=\"M46.1746 24.5C47.0875 24.5 47.8293 24.4144 48.7137 24.2432V20.9334C48.1717
+ 21.1331 47.5725 21.1902 47.0019 21.1902C45.604 21.1902 45.0904 20.5054 45.0904
+ 19.1929V12.4592H48.7137V9.14945H45.0904V4.24185H41.0962V9.14945H38.5V12.4592H41.0962V19.5638C41.0962
+ 22.7024 42.751 24.5 46.1746 24.5Z\" fill=\"#232222\"></path><path d=\"M55.2368
+ 24.5C57.3766 24.5 58.8031 23.7867 59.5163 22.6739C59.5734 23.159 59.7446 23.8152
+ 59.9728 24.2147H63.5676C63.3679 23.5299 63.2252 22.4456 63.2252 21.7323V13.9144C63.2252
+ 10.6902 60.9143 8.86412 57.0057 8.86412C54.0386 8.86412 51.7277 9.9769 50.4438
+ 11.6033L52.7262 13.8573C53.7533 12.716 55.0657 12.1168 56.6633 12.1168C58.6034
+ 12.1168 59.4593 12.7731 59.4593 13.7432C59.4593 14.5706 58.8887 15.1413 55.8074
+ 15.1413C52.8403 15.1413 49.8447 16.3682 49.8447 19.7921C49.8447 22.9592 52.1271
+ 24.5 55.2368 24.5ZM56.0357 21.4755C54.4951 21.4755 53.7818 20.8193 53.7818
+ 19.6494C53.7818 18.6223 54.6377 17.9375 56.0642 17.9375C58.3466 17.9375 58.9743
+ 17.7948 59.4593 17.3098V18.5367C59.4593 20.163 58.0898 21.4755 56.0357 21.4755Z\"
+ fill=\"#232222\"></path><path d=\"M65.8217 7.23777H69.9871V3.5H65.8217V7.23777ZM65.9073
+ 24.2147H69.9015V9.14945H65.9073V24.2147Z\" fill=\"#232222\"></path><path d=\"M72.8113
+ 24.2147H76.8055V3.95652H72.8113V24.2147Z\" fill=\"#232222\"></path><path d=\"M85.1075
+ 24.5C89.5011 24.5 91.755 22.1888 91.755 19.7636C91.755 17.5666 90.6423 15.9402
+ 86.9905 15.284C84.4228 14.8274 82.8251 14.2568 82.8251 13.3723C82.8251 12.6019
+ 83.7381 12.0598 85.2787 12.0598C86.6481 12.0598 87.7893 12.5163 88.6452 13.5149L91.1844
+ 11.375C89.9005 9.77717 87.7893 8.86412 85.2787 8.86412C81.4271 8.86412 79.0306
+ 10.9185 79.0306 13.4293C79.0306 16.1685 81.5412 17.3954 84.2516 17.8804C86.5055
+ 18.2799 87.7608 18.7079 87.7608 19.678C87.7608 20.5054 86.8763 21.1902 85.1931
+ 21.1902C83.5383 21.1902 82.2545 20.4198 81.6554 19.2785L78.2888 20.8763C79.1162
+ 22.9307 81.9407 24.5 85.1075 24.5Z\" fill=\"#232222\"></path><path d=\"M100.685
+ 24.5C103.538 24.5 105.592 23.3587 107.132 20.7907L103.937 19.1644C103.338
+ 20.3628 102.368 21.1902 100.685 21.1902C98.1455 21.1902 96.776 19.1359 96.776
+ 16.6535C96.776 14.1712 98.2311 12.1739 100.685 12.1739C102.225 12.1739 103.252
+ 13.0014 103.851 14.2568L107.075 12.5448C105.991 10.1766 103.851 8.86412 100.685
+ 8.86412C95.4636 8.86412 92.7818 12.6875 92.7818 16.6535C92.7818 20.9619 96.0342
+ 24.5 100.685 24.5Z\" fill=\"#232222\"></path><path d=\"M113.495 24.5C115.635
+ 24.5 117.061 23.7867 117.774 22.6739C117.831 23.159 118.003 23.8152 118.231
+ 24.2147H121.826C121.626 23.5299 121.483 22.4456 121.483 21.7323V13.9144C121.483
+ 10.6902 119.172 8.86412 115.264 8.86412C112.297 8.86412 109.986 9.9769 108.702
+ 11.6033L110.984 13.8573C112.011 12.716 113.324 12.1168 114.921 12.1168C116.861
+ 12.1168 117.717 12.7731 117.717 13.7432C117.717 14.5706 117.147 15.1413 114.065
+ 15.1413C111.098 15.1413 108.103 16.3682 108.103 19.7921C108.103 22.9592 110.385
+ 24.5 113.495 24.5ZM114.294 21.4755C112.753 21.4755 112.04 20.8193 112.04 19.6494C112.04
+ 18.6223 112.896 17.9375 114.322 17.9375C116.605 17.9375 117.232 17.7948 117.717
+ 17.3098V18.5367C117.717 20.163 116.348 21.4755 114.294 21.4755Z\" fill=\"#232222\"></path><path
+ d=\"M123.831 24.2147H127.825V3.95652H123.831V24.2147Z\" fill=\"#232222\"></path><path
+ d=\"M137.411 24.5C140.35 24.5 142.718 23.3301 144.03 21.2473L141.006 19.3071C140.207
+ 20.5625 139.151 21.2473 137.411 21.2473C135.442 21.2473 134.016 20.163 133.645
+ 18.0516H145V16.6535C145 12.6875 142.575 8.86412 137.382 8.86412C132.447 8.86412
+ 129.765 12.716 129.765 16.6821C129.765 22.3315 134.016 24.5 137.411 24.5ZM133.788
+ 14.913C134.415 13.0014 135.728 12.1168 137.468 12.1168C139.437 12.1168 140.635
+ 13.2867 141.034 14.913H133.788Z\" fill=\"#232222\"></path></svg></a><div class=\"flex
+ w-full items-center justify-end gap-3 xl-small:justify-between\"><nav class=\"website-nav
+ m-auto hidden xl-small:relative xl-small:block\"><ul class=\"flex gap-x-[4px]\"><li><button
+ type=\"button\" class=\"flex items-center gap-[4px] rounded-md px-3 py-2.5
+ text-sm font-medium hover:bg-gray-200\">Platform<svg width=\"14\" height=\"14\"
+ viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path
+ d=\"M2.91675 7H11.0834\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"
+ style=\"opacity:1;transform:scaleY(1);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></button><div
+ class=\"pointer-events-none absolute left-1/2 top-full z-[100] mt-3 -translate-x-1/2
+ pt-4 opacity-0\"><div class=\"overflow-hidden rounded-2xl border border-gray-200
+ shadow-8\"><div class=\"grid auto-cols-[268px] grid-flow-col gap-4 border-b
+ border-gray-200 bg-white p-8 shadow-2\"><div class=\"flex flex-col gap-4\"><div
+ class=\"flex items-center gap-2.5 px-3\"><p class=\"whitespace-nowrap font-mdio
+ text-[14px] font-medium uppercase !tracking-[0.6px] text-gray-700/80\">Meet
+ Tailscale</p><div class=\"h-px w-full bg-gray-200\"></div></div><ul class=\"flex
+ flex-col gap-1\"><li><a class=\"block rounded-md p-3 text-base font-medium
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "How Tailscale Works"}\" href=\"/blog/how-tailscale-works\">How
+ Tailscale Works</a></li><li><a class=\"block rounded-md p-3 text-base font-medium
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "WireGuard\xAE for Enterprises"}\" href=\"/wireguard-vpn\">WireGuard\xAE
+ for Enterprises</a></li><li><a class=\"block rounded-md p-3 text-base font-medium
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Features"}\" href=\"/features\">Features</a></li><li><a class=\"block
+ rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Integrations"}\"
+ href=\"/integrations\">Integrations</a></li><li><a class=\"block rounded-md
+ p-3 text-base font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Docs"}\"
+ href=\"/docs\">Docs</a></li><li><a class=\"block rounded-md p-3 text-base
+ font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Download"}\" href=\"/download\">Download</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Compare
+ Tailscale"}\" href=\"/compare\">Compare Tailscale</a></li></ul></div><div
+ class=\"flex flex-col gap-4\"><div class=\"flex items-center gap-2.5 px-3\"><p
+ class=\"whitespace-nowrap font-mdio text-[14px] font-medium uppercase !tracking-[0.6px]
+ text-gray-700/80\">Products</p><div class=\"h-px w-full bg-gray-200\"></div></div><ul
+ class=\"flex flex-col gap-1\"><li><a class=\"block rounded-md p-3 text-base
+ font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Business VPN"}\" href=\"/use-cases/business-vpn\">Business
+ VPN</a></li><li><a class=\"block rounded-md p-3 text-base font-medium text-gray-700
+ hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "PAM"}\" href=\"/use-cases/pam\">PAM</a></li><li><a class=\"block
+ rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "CI/CD Connectivity"}\"
+ href=\"/use-cases/ci-cd\">CI/CD Connectivity</a></li><li><a class=\"block
+ rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Secure Access
+ to AI"}\" href=\"/use-cases/securing-ai\">Secure Access to AI</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Cloud
+ Connectivity"}\" href=\"/use-cases/cloud-connectivity\">Cloud Connectivity</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Workload
+ Connectivity"}\" href=\"/use-cases/workload-connectivity\">Workload Connectivity</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Edge
+ & IoT"}\" href=\"/use-cases/edge-iot\">Edge & IoT</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Homelab"}\"
+ href=\"/use-cases/homelab\">Homelab</a></li></ul></div><a class=\"flex flex-col
+ self-stretch overflow-hidden rounded-md border border-gray-200 bg-gray-100
+ shadow-2\" href=\"https://tailscale.com/use-cases/securing-ai\"><div class=\"relative
+ flex flex-1 items-center\"><img alt=\"aperture dashboard\" loading=\"lazy\"
+ width=\"268\" height=\"150\" decoding=\"async\" data-nimg=\"1\" class=\"h-auto
+ w-full rounded-none border-none object-cover\" style=\"color:transparent\"
+ src=\"https://cdn.sanity.io/images/w77i7m8x/production-v2/7fbf14ac75bdf258eeff9efb2a2d5bbbf2e87098-266x342.svg\"/></div><div
+ class=\"bg-gray-50 p-8\"><p class=\"mb-1 font-mdio text-[14px] font-medium
+ uppercase !tracking-[0.6px] text-gray-700\">Aperture by Tailscale</p><p class=\"text-base
+ text-gray-700/80\">Unified AI governance for AI agents and users.</p></div></a></div><div
+ class=\"bg-gray-50 p-8\"><div class=\"flex items-center justify-between\"><div
+ class=\"flex flex-col gap-1\"><p class=\"font-mdio text-[14px] font-medium
+ uppercase !tracking-[0.6px] text-gray-700\">Tailscale Winter Update</p><p
+ class=\"text-base text-gray-700/80\">The latest improvements on Aperture,
+ Peer Relays, Services, and more.</p></div><a class=\"inline-flex h-9 shrink-0
+ items-center justify-center rounded-md border border-gray-800 bg-gray-800
+ px-3 text-base font-medium text-white shadow-2 transition-all duration-200
+ hover:opacity-80 hover:shadow-4\" href=\"https://tailscale.com/winter-update-week-26\">Read
+ more</a></div></div></div></div></li><li><button type=\"button\" class=\"flex
+ items-center gap-[4px] rounded-md px-3 py-2.5 text-sm font-medium hover:bg-gray-200\">Customer
+ Stories<svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\"
+ xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2.91675 7H11.0834\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path><path
+ d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\" style=\"opacity:1;transform:scaleY(1);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></button><div
+ class=\"pointer-events-none absolute left-1/2 top-full z-[100] mt-3 -translate-x-1/2
+ pt-4 opacity-0\"><div class=\"overflow-hidden rounded-2xl border border-gray-200
+ shadow-8\"><div class=\"grid auto-cols-[268px] grid-flow-col gap-4 border-b
+ border-gray-200 bg-white p-8 shadow-2\"><div class=\"flex flex-col gap-4\"><div
+ class=\"flex items-center gap-2.5 px-3\"><p class=\"whitespace-nowrap font-mdio
+ text-[14px] font-medium uppercase !tracking-[0.6px] text-gray-700/80\">Customer
+ Stories</p><div class=\"h-px w-full bg-gray-200\"></div></div><ul class=\"flex
+ flex-col gap-1\"><li><a class=\"block rounded-md p-3 text-base font-medium
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Instacart"}\" href=\"/customers/instacart\">Instacart</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Cribl"}\"
+ href=\"/customers/cribl\">Cribl</a></li><li><a class=\"block rounded-md p-3
+ text-base font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Mercury"}\"
+ href=\"/customers/mercury\">Mercury</a></li><li><a class=\"block rounded-md
+ p-3 text-base font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Hugging Face"}\"
+ href=\"/customers/hugging-face\">Hugging Face</a></li><li><a class=\"block
+ rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "All Customer Stories"}\"
+ href=\"/customers\">All Customer Stories</a></li></ul></div></div></div></div></li><li><button
+ type=\"button\" class=\"flex items-center gap-[4px] rounded-md px-3 py-2.5
+ text-sm font-medium hover:bg-gray-200\">Community<svg width=\"14\" height=\"14\"
+ viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path
+ d=\"M2.91675 7H11.0834\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"
+ style=\"opacity:1;transform:scaleY(1);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></button><div
+ class=\"pointer-events-none absolute left-1/2 top-full z-[100] mt-3 -translate-x-1/2
+ pt-4 opacity-0\"><div class=\"overflow-hidden rounded-2xl border border-gray-200
+ shadow-8\"><div class=\"grid auto-cols-[268px] grid-flow-col gap-4 border-b
+ border-gray-200 bg-white p-8 shadow-2\"><div class=\"flex flex-col gap-4\"><div
+ class=\"flex items-center gap-2.5 px-3\"><p class=\"whitespace-nowrap font-mdio
+ text-[14px] font-medium uppercase !tracking-[0.6px] text-gray-700/80\">Join
+ the Community</p><div class=\"h-px w-full bg-gray-200\"></div></div><ul class=\"flex
+ flex-col gap-1\"><li><a class=\"block rounded-md p-3 text-base font-medium
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Tailscale Insiders"}\" href=\"/insiders-program\">Tailscale Insiders</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Community
+ Projects"}\" href=\"/community/community-projects\">Community Projects</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Bring
+ Tailscale to Work"}\" href=\"/bring-tailscale-to-work\">Bring Tailscale
+ to Work</a></li></ul></div><div class=\"flex flex-col gap-4\"><div class=\"flex
+ items-center gap-2.5 px-3\"><p class=\"whitespace-nowrap font-mdio text-[14px]
+ font-medium uppercase !tracking-[0.6px] text-gray-700/80\">Events</p><div
+ class=\"h-px w-full bg-gray-200\"></div></div><ul class=\"flex flex-col gap-1\"><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Events
+ and Webinars"}\" href=\"/events-webinars\">Events and Webinars</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "TailscaleUp"}\"
+ href=\"/tailscaleup\">TailscaleUp</a></li></ul></div><div class=\"flex flex-col
+ gap-4\"><div class=\"flex items-center gap-2.5 px-3\"><p class=\"whitespace-nowrap
+ font-mdio text-[14px] font-medium uppercase !tracking-[0.6px] text-gray-700/80\">Learn
+ more</p><div class=\"h-px w-full bg-gray-200\"></div></div><ul class=\"flex
+ flex-col gap-1\"><li><a class=\"block rounded-md p-3 text-base font-medium
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Docs"}\" href=\"/docs\">Docs</a></li><li><a class=\"block rounded-md
+ p-3 text-base font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Blog"}\"
+ href=\"/blog\">Blog</a></li><li><a class=\"block rounded-md p-3 text-base
+ font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Changelog"}\" href=\"/changelog\">Changelog</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Press"}\"
+ href=\"/press\">Press</a></li></ul></div></div><div class=\"bg-gray-50 p-8\"><div
+ class=\"flex items-center justify-between\"><div class=\"flex flex-col gap-1\"><p
+ class=\"font-mdio text-[14px] font-medium uppercase !tracking-[0.6px] text-gray-700\">Join
+ us at TailscaleUp</p><p class=\"text-base text-gray-700/80\">Tailscale\u2019s
+ conference for engineering, security, and IT leaders.</p></div><a class=\"inline-flex
+ h-9 shrink-0 items-center justify-center rounded-md border border-gray-800
+ bg-gray-800 px-3 text-base font-medium text-white shadow-2 transition-all
+ duration-200 hover:opacity-80 hover:shadow-4\" href=\"https://tailscale.com/tailscaleup\">Learn
+ more</a></div></div></div></div></li><li><button type=\"button\" class=\"flex
+ items-center gap-[4px] rounded-md px-3 py-2.5 text-sm font-medium hover:bg-gray-200\">Partnerships<svg
+ width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path
+ d=\"M2.91675 7H11.0834\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"
+ style=\"opacity:1;transform:scaleY(1);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></button><div
+ class=\"pointer-events-none absolute left-1/2 top-full z-[100] mt-3 -translate-x-1/2
+ pt-4 opacity-0\"><div class=\"overflow-hidden rounded-2xl border border-gray-200
+ shadow-8\"><div class=\"grid auto-cols-[268px] grid-flow-col gap-4 border-b
+ border-gray-200 bg-white p-8 shadow-2\"><div class=\"flex flex-col gap-4\"><div
+ class=\"flex items-center gap-2.5 px-3\"><p class=\"whitespace-nowrap font-mdio
+ text-[14px] font-medium uppercase !tracking-[0.6px] text-gray-700/80\">Partner
+ Opportunities</p><div class=\"h-px w-full bg-gray-200\"></div></div><ul class=\"flex
+ flex-col gap-1\"><li><a class=\"block rounded-md p-3 text-base font-medium
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Become a Partner"}\" href=\"/partnerships\">Become a Partner</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Community
+ Projects"}\" href=\"/community/community-projects\">Community Projects</a></li><li><a
+ class=\"block rounded-md p-3 text-base font-medium text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Integrations"}\"
+ href=\"/integrations\">Integrations</a></li><li><a class=\"block rounded-md
+ p-3 text-base font-medium text-gray-700 hover:bg-gray-200\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Contact Partnerships
+ Team"}\" href=\"/contact/partnerships\">Contact Partnerships Team</a></li></ul></div></div></div></div></li><li><a
+ href=\"/pricing\" class=\"flex items-center gap-4 rounded-md px-3 py-2.5 text-sm
+ font-medium hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Pricing"}\">Pricing</a></li></ul></nav><nav class=\"hidden sm:ml-auto
+ sm:block lg:ml-0\"><ul class=\"flex items-center gap-x-[4px] font-medium\"><li><a
+ href=\"https://login.tailscale.com/welcome\" class=\"block rounded-md px-3
+ py-2.5 text-sm hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Login"}\">Login</a></li><li><a class=\"inline-flex h-9 items-center
+ justify-center rounded-[0.5rem] border border-gray-800 bg-gray-800 px-3 text-base
+ font-medium text-white shadow-2 transition-all duration-200 hover:opacity-80
+ hover:shadow-4\" href=\"https://login.tailscale.com/start\">Get started -
+ it's free!</a></li></ul></nav><button type=\"button\" class=\"h-[36px]
+ rounded-[0.5rem] bg-gray-800 px-[5px] xl-small:hidden\"><svg width=\"24\"
+ height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path
+ d=\"M4 12H20\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M4 6H20\" stroke=\"#fff\" stroke-width=\"2\"
+ stroke-linecap=\"round\" stroke-linejoin=\"round\"></path><path d=\"M4 18H20\"
+ stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg></button></div></div></header><div
+ class=\"pointer-events-none absolute left-0 top-full w-full bg-white opacity-0
+ transition-opacity duration-150 md:hidden\"><div class=\"bg-gray-700\"><nav><ul
+ class=\"flex justify-center gap-6\"><li><a class=\"block py-2.5 font-mdio
+ text-xs uppercase !tracking-[0.6px] text-gray-0/80 hover:underline\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Blog"}\"
+ href=\"/blog\">Blog</a></li><li><a class=\"block py-2.5 font-mdio text-xs
+ uppercase !tracking-[0.6px] text-gray-0/80 hover:underline\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Docs"}\"
+ href=\"/docs\">Docs</a></li><li><a class=\"block py-2.5 font-mdio text-xs
+ uppercase !tracking-[0.6px] text-gray-0/80 hover:underline\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Download"}\"
+ href=\"/download\">Download</a></li><li><a class=\"block py-2.5 font-mdio
+ text-xs uppercase !tracking-[0.6px] text-gray-0/80 hover:underline\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Contact Sales"}\"
+ href=\"/contact/sales\">Contact Sales</a></li></ul></nav></div><nav class=\"max-h-[60vh]
+ overflow-auto rounded-b-md p-6\"><details class=\"group\"><summary class=\"flex
+ items-center justify-between rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] hover:bg-gray-200 group-open:bg-gray-200\">Platform<span
+ class=\"relative\"><span class=\"group-open:hidden\"><svg width=\"14\" height=\"14\"
+ viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path
+ d=\"M2.91675 7H11.0834\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"
+ style=\"opacity:1;transform:scaleY(1);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></span><span
+ class=\"hidden group-open:block\"><svg width=\"14\" height=\"14\" viewBox=\"0
+ 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2.91675
+ 7H11.0834\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"
+ style=\"opacity:0;transform:scaleY(0);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></span></span></summary><div
+ class=\"mb-4 border-b border-gray-200 py-4\"><div class=\"mb-6\"><p class=\"mb-3
+ flex items-center gap-2.5 whitespace-nowrap px-2 font-mdio text-[12px] font-medium
+ uppercase !tracking-[0.6px] text-gray-700/80 after:h-px after:w-full after:bg-gray-200
+ after:content-['']\">Meet Tailscale</p><ul><li class=\"space-y-1\"><a
+ class=\"block rounded-md px-2 py-3 text-[14px] font-medium tracking-[-0.14px]
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "How Tailscale Works"}\" href=\"/blog/how-tailscale-works\">How
+ Tailscale Works</a></li><li class=\"space-y-1\"><a class=\"block rounded-md
+ px-2 py-3 text-[14px] font-medium tracking-[-0.14px] text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "WireGuard\xAE
+ for Enterprises"}\" href=\"/wireguard-vpn\">WireGuard\xAE for Enterprises</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Features"}\" href=\"/features\">Features</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Integrations"}\" href=\"/integrations\">Integrations</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Docs"}\" href=\"/docs\">Docs</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Download"}\" href=\"/download\">Download</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Compare Tailscale"}\"
+ href=\"/compare\">Compare Tailscale</a></li></ul></div><div class=\"mb-6\"><p
+ class=\"mb-3 flex items-center gap-2.5 whitespace-nowrap px-2 font-mdio text-[12px]
+ font-medium uppercase !tracking-[0.6px] text-gray-700/80 after:h-px after:w-full
+ after:bg-gray-200 after:content-['']\">Products</p><ul><li class=\"space-y-1\"><a
+ class=\"block rounded-md px-2 py-3 text-[14px] font-medium tracking-[-0.14px]
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Business VPN"}\" href=\"/use-cases/business-vpn\">Business VPN</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "PAM"}\" href=\"/use-cases/pam\">PAM</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "CI/CD Connectivity"}\"
+ href=\"/use-cases/ci-cd\">CI/CD Connectivity</a></li><li class=\"space-y-1\"><a
+ class=\"block rounded-md px-2 py-3 text-[14px] font-medium tracking-[-0.14px]
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Secure Access to AI"}\" href=\"/use-cases/securing-ai\">Secure
+ Access to AI</a></li><li class=\"space-y-1\"><a class=\"block rounded-md px-2
+ py-3 text-[14px] font-medium tracking-[-0.14px] text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Cloud
+ Connectivity"}\" href=\"/use-cases/cloud-connectivity\">Cloud Connectivity</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Workload Connectivity"}\"
+ href=\"/use-cases/workload-connectivity\">Workload Connectivity</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Edge & IoT"}\"
+ href=\"/use-cases/edge-iot\">Edge & IoT</a></li><li class=\"space-y-1\"><a
+ class=\"block rounded-md px-2 py-3 text-[14px] font-medium tracking-[-0.14px]
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Homelab"}\" href=\"/use-cases/homelab\">Homelab</a></li></ul></div></div></details><details
+ class=\"group\"><summary class=\"flex items-center justify-between rounded-md
+ px-2 py-3 text-[14px] font-medium tracking-[-0.14px] hover:bg-gray-200 group-open:bg-gray-200\">Customer
+ Stories<span class=\"relative\"><span class=\"group-open:hidden\"><svg width=\"14\"
+ height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path
+ d=\"M2.91675 7H11.0834\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"
+ style=\"opacity:1;transform:scaleY(1);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></span><span
+ class=\"hidden group-open:block\"><svg width=\"14\" height=\"14\" viewBox=\"0
+ 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2.91675
+ 7H11.0834\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"
+ style=\"opacity:0;transform:scaleY(0);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></span></span></summary><div
+ class=\"mb-4 border-b border-gray-200 py-4\"><div class=\"mb-6\"><p class=\"mb-3
+ flex items-center gap-2.5 whitespace-nowrap px-2 font-mdio text-[12px] font-medium
+ uppercase !tracking-[0.6px] text-gray-700/80 after:h-px after:w-full after:bg-gray-200
+ after:content-['']\">Customer Stories</p><ul><li class=\"space-y-1\"><a
+ class=\"block rounded-md px-2 py-3 text-[14px] font-medium tracking-[-0.14px]
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Instacart"}\" href=\"/customers/instacart\">Instacart</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Cribl"}\" href=\"/customers/cribl\">Cribl</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Mercury"}\" href=\"/customers/mercury\">Mercury</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Hugging Face"}\" href=\"/customers/hugging-face\">Hugging
+ Face</a></li><li class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3
+ text-[14px] font-medium tracking-[-0.14px] text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "All
+ Customer Stories"}\" href=\"/customers\">All Customer Stories</a></li></ul></div></div></details><details
+ class=\"group\"><summary class=\"flex items-center justify-between rounded-md
+ px-2 py-3 text-[14px] font-medium tracking-[-0.14px] hover:bg-gray-200 group-open:bg-gray-200\">Community<span
+ class=\"relative\"><span class=\"group-open:hidden\"><svg width=\"14\" height=\"14\"
+ viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path
+ d=\"M2.91675 7H11.0834\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"
+ style=\"opacity:1;transform:scaleY(1);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></span><span
+ class=\"hidden group-open:block\"><svg width=\"14\" height=\"14\" viewBox=\"0
+ 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2.91675
+ 7H11.0834\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"
+ style=\"opacity:0;transform:scaleY(0);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></span></span></summary><div
+ class=\"mb-4 border-b border-gray-200 py-4\"><div class=\"mb-6\"><p class=\"mb-3
+ flex items-center gap-2.5 whitespace-nowrap px-2 font-mdio text-[12px] font-medium
+ uppercase !tracking-[0.6px] text-gray-700/80 after:h-px after:w-full after:bg-gray-200
+ after:content-['']\">Join the Community</p><ul><li class=\"space-y-1\"><a
+ class=\"block rounded-md px-2 py-3 text-[14px] font-medium tracking-[-0.14px]
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Tailscale Insiders"}\" href=\"/insiders-program\">Tailscale Insiders</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Community Projects"}\"
+ href=\"/community/community-projects\">Community Projects</a></li><li class=\"space-y-1\"><a
+ class=\"block rounded-md px-2 py-3 text-[14px] font-medium tracking-[-0.14px]
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Bring Tailscale to Work"}\" href=\"/bring-tailscale-to-work\">Bring
+ Tailscale to Work</a></li></ul></div><div class=\"mb-6\"><p class=\"mb-3 flex
+ items-center gap-2.5 whitespace-nowrap px-2 font-mdio text-[12px] font-medium
+ uppercase !tracking-[0.6px] text-gray-700/80 after:h-px after:w-full after:bg-gray-200
+ after:content-['']\">Events</p><ul><li class=\"space-y-1\"><a class=\"block
+ rounded-md px-2 py-3 text-[14px] font-medium tracking-[-0.14px] text-gray-700
+ hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Events and Webinars"}\" href=\"/events-webinars\">Events and Webinars</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "TailscaleUp"}\" href=\"/tailscaleup\">TailscaleUp</a></li></ul></div><div
+ class=\"mb-6\"><p class=\"mb-3 flex items-center gap-2.5 whitespace-nowrap
+ px-2 font-mdio text-[12px] font-medium uppercase !tracking-[0.6px] text-gray-700/80
+ after:h-px after:w-full after:bg-gray-200 after:content-['']\">Learn
+ more</p><ul><li class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3
+ text-[14px] font-medium tracking-[-0.14px] text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Docs"}\"
+ href=\"/docs\">Docs</a></li><li class=\"space-y-1\"><a class=\"block rounded-md
+ px-2 py-3 text-[14px] font-medium tracking-[-0.14px] text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Blog"}\"
+ href=\"/blog\">Blog</a></li><li class=\"space-y-1\"><a class=\"block rounded-md
+ px-2 py-3 text-[14px] font-medium tracking-[-0.14px] text-gray-700 hover:bg-gray-200\"
+ data-track=\"Link Clicked\" data-track-properties=\"{"label": "Changelog"}\"
+ href=\"/changelog\">Changelog</a></li><li class=\"space-y-1\"><a class=\"block
+ rounded-md px-2 py-3 text-[14px] font-medium tracking-[-0.14px] text-gray-700
+ hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Press"}\" href=\"/press\">Press</a></li></ul></div></div></details><details
+ class=\"group\"><summary class=\"flex items-center justify-between rounded-md
+ px-2 py-3 text-[14px] font-medium tracking-[-0.14px] hover:bg-gray-200 group-open:bg-gray-200\">Partnerships<span
+ class=\"relative\"><span class=\"group-open:hidden\"><svg width=\"14\" height=\"14\"
+ viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path
+ d=\"M2.91675 7H11.0834\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"
+ style=\"opacity:1;transform:scaleY(1);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></span><span
+ class=\"hidden group-open:block\"><svg width=\"14\" height=\"14\" viewBox=\"0
+ 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2.91675
+ 7H11.0834\" stroke=\"#2E2D2D\" stroke-width=\"1.16667\" stroke-linecap=\"round\"
+ stroke-linejoin=\"round\"></path><path d=\"M7 2.9165V11.0832\" stroke=\"#2E2D2D\"
+ stroke-width=\"1.16667\" stroke-linecap=\"round\" stroke-linejoin=\"round\"
+ style=\"opacity:0;transform:scaleY(0);transform-origin:center;transition:opacity
+ 150ms ease-in-out, transform 150ms ease-in-out\"></path></svg></span></span></summary><div
+ class=\"mb-4 border-b border-gray-200 py-4\"><div class=\"mb-6\"><p class=\"mb-3
+ flex items-center gap-2.5 whitespace-nowrap px-2 font-mdio text-[12px] font-medium
+ uppercase !tracking-[0.6px] text-gray-700/80 after:h-px after:w-full after:bg-gray-200
+ after:content-['']\">Partner Opportunities</p><ul><li class=\"space-y-1\"><a
+ class=\"block rounded-md px-2 py-3 text-[14px] font-medium tracking-[-0.14px]
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Become a Partner"}\" href=\"/partnerships\">Become a Partner</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Community Projects"}\"
+ href=\"/community/community-projects\">Community Projects</a></li><li class=\"space-y-1\"><a
+ class=\"block rounded-md px-2 py-3 text-[14px] font-medium tracking-[-0.14px]
+ text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Integrations"}\" href=\"/integrations\">Integrations</a></li><li
+ class=\"space-y-1\"><a class=\"block rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] text-gray-700 hover:bg-gray-200\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Contact Partnerships Team"}\"
+ href=\"/contact/partnerships\">Contact Partnerships Team</a></li></ul></div></div></details><a
+ class=\"flex justify-between rounded-md px-2 py-3 text-[14px] font-medium
+ tracking-[-0.14px] hover:bg-gray-200\" data-track=\"Link Clicked\" data-track-properties=\"{"label":
+ "Pricing"}\" href=\"/pricing\">Pricing</a></nav><nav class=\"mx-6
+ border-t border-gray-200 py-6\"><ul class=\"flex items-center justify-between
+ gap-x-4 font-medium\"><li><a href=\"https://login.tailscale.com/welcome\"
+ class=\"block rounded-md px-3 py-2.5 text-sm hover:bg-gray-200\" data-track=\"Link
+ Clicked\" data-track-properties=\"{"label": "Login"}\">Login</a></li><li><a
+ class=\"inline-flex h-9 items-center justify-center rounded-md border border-gray-800
+ bg-gray-800 px-3 text-base font-medium text-white shadow-2 transition-all
+ duration-200 hover:opacity-80 hover:shadow-4\" href=\"https://login.tailscale.com/start\">Get
+ started - it's free!</a></li></ul></nav></div></div><div class=\"website-nav-backdrop
+ z-100 pointer-events-none fixed left-0 top-0 h-full w-full bg-gray-1000/25
+ opacity-0 transition-opacity duration-300\"></div><main><div class=\"p-top-115
+ bg-red-900\"><div class=\"container flex flex-wrap justify-between\"><div
+ class=\"flex-1 pt-12 lg:pt-[66px]\"><div class=\"mb-8 flex justify-center
+ md:justify-start lg:mb-[51px]\"><div class=\"md:-y-3 inline-flex items-center
+ justify-center gap-[17px] rounded-[10px] bg-red-600 p-3 md:px-5\"><img alt=\"404
+ icon\" loading=\"lazy\" width=\"27\" height=\"38\" decoding=\"async\" data-nimg=\"1\"
+ class=\"undefined\" style=\"color:transparent;background-size:cover;background-position:50%
+ 50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg
+ xmlns='http://www.w3.org/2000/svg' viewBox='0 0 27 38'%3E%3Cfilter
+ id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur
+ stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1
+ 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0'
+ y='0' width='100%25' height='100%25'/%3E%3CfeComposite
+ operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur
+ stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25'
+ height='100%25' x='0' y='0' preserveAspectRatio='none'
+ style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAwAB/ucNC4UAAAAASUVORK5CYII='/%3E%3C/svg%3E")\"
+ srcSet=\"https://cdn.sanity.io/images/w77i7m8x/production/c47871628e0abd711d3b18acf850972fff8c7d1c-27x38.svg?w=32&q=75&fit=clip&auto=format
+ 1x, https://cdn.sanity.io/images/w77i7m8x/production/c47871628e0abd711d3b18acf850972fff8c7d1c-27x38.svg?w=64&q=75&fit=clip&auto=format
+ 2x\" src=\"https://cdn.sanity.io/images/w77i7m8x/production/c47871628e0abd711d3b18acf850972fff8c7d1c-27x38.svg?w=64&q=75&fit=clip&auto=format\"/><div
+ class=\"t-52 font-medium text-white\">404</div></div></div><div class=\"text-left
+ mx-0\"><h2 class=\"t-80 text-white mb-4 md:mb-2.5\" style=\"max-width:595px\">Network
+ (dis)connected</h2><div class=\"t-b18 text-white/70 mb-5 lg:mb-9\" style=\"max-width:499px\"><div
+ class=\"content-prose\"><p>Don\u2019t worry, you haven\u2019t broken anything.
+ This page has simply disappeared into thin air. Head back to our homepage,
+ and start again.</p></div></div><div class=\"w-full xs:w-auto flex justify-start\"><div
+ class=\"flex w-full flex-col gap-y-4 xs:w-auto xs:flex-row xs:items-center
+ xs:space-x-5 md:space-x-[30px]\"><a target=\"\" data-track=\"Link Clicked\"
+ data-track-properties=\"{"label": "Take me back"}\" href=\"/\"><div
+ class=\"inline-flex shrink-0 border transition-colors duration-200 w-full
+ xs:w-auto items-center justify-center text-center relative focus:outline-none
+ group transition-all overflow-hidden font-body font-medium rounded-lg group
+ gap-[11px] py-2 px-[17px] leading-[1.46] tracking-[-0.16px] bg-heading-white
+ border-white text-heading-black hover:bg-gray-50 hover:border-gray-50\"><div>Take
+ me back</div><div class=\"relative \"><span class=\"block will-change-transform\"
+ style=\"opacity:1;transform:none\"><svg xmlns=\"http://www.w3.org/2000/svg\"
+ viewBox=\"0 0 16 18\" class=\"w-4\"><use href=\"/sprite.svg#arrowRight\"></use></svg></span><span
+ class=\"absolute inset-0 block will-change-transform\" style=\"opacity:0;transform:translateX(-15px)
+ translateY(0px) translateZ(0)\"><svg xmlns=\"http://www.w3.org/2000/svg\"
+ viewBox=\"0 0 16 18\" class=\"w-4\"><use href=\"/sprite.svg#arrowRight\"></use></svg></span></div></div></a></div></div></div></div><div
+ class=\"mt-6 w-full max-w-[730px] shrink-0 md:mt-0 laptop:w-[593px]\"><img
+ _type=\"asset\" video=\"[object Object]\" alt=\"404\" loading=\"lazy\" width=\"730\"
+ height=\"730\" decoding=\"async\" data-nimg=\"1\" class=\"md:block mx-auto\"
+ style=\"color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg
+ xmlns='http://www.w3.org/2000/svg' viewBox='0 0 730 730'%3E%3Cfilter
+ id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur
+ stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1
+ 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0'
+ y='0' width='100%25' height='100%25'/%3E%3CfeComposite
+ operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur
+ stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25'
+ height='100%25' x='0' y='0' preserveAspectRatio='none'
+ style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAwAB/ucNC4UAAAAASUVORK5CYII='/%3E%3C/svg%3E")\"
+ srcSet=\"https://cdn.sanity.io/images/w77i7m8x/production/00314739bf834a0d9e0a67cd383f295181584c64-730x730.svg?w=750&q=75&fit=clip&auto=format
+ 1x, https://cdn.sanity.io/images/w77i7m8x/production/00314739bf834a0d9e0a67cd383f295181584c64-730x730.svg?w=1920&q=75&fit=clip&auto=format
+ 2x\" src=\"https://cdn.sanity.io/images/w77i7m8x/production/00314739bf834a0d9e0a67cd383f295181584c64-730x730.svg?w=1920&q=75&fit=clip&auto=format\"/></div></div></div></main><footer
+ class=\"mt-16 bg-white py-12\"><div class=\"mx-auto w-full max-w-[1360px]
+ px-6 md:max-w-[1440px] md:px-16\"><div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2
+ md:grid-cols-12\"><div class=\"px-3 sm:col-span-2 md:col-span-4 md:px-8 md:py-8\"><a
+ class=\"block w-[110px]\" title=\"Homepage\" href=\"/\"><svg width=\"48\"
+ height=\"48\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"
+ aria-hidden=\"true\"><path d=\"M3 15C4.65685 15 6 13.6569 6 12C6 10.3431 4.65685
+ 9 3 9C1.34315 9 0 10.3431 0 12C0 13.6569 1.34315 15 3 15Z\" fill=\"currentColor\"></path><path
+ d=\"M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9
+ 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z\" fill=\"currentColor\"></path><path
+ d=\"M12 24C13.6569 24 15 22.6569 15 21C15 19.3431 13.6569 18 12 18C10.3431
+ 18 9 19.3431 9 21C9 22.6569 10.3431 24 12 24Z\" fill=\"currentColor\"></path><path
+ d=\"M21 15C22.6569 15 24 13.6569 24 12C24 10.3431 22.6569 9 21 9C19.3431 9
+ 18 10.3431 18 12C18 13.6569 19.3431 15 21 15Z\" fill=\"currentColor\"></path><path
+ d=\"M3 24C4.65685 24 6 22.6569 6 21C6 19.3431 4.65685 18 3 18C1.34315 18 0
+ 19.3431 0 21C0 22.6569 1.34315 24 3 24Z\" fill=\"currentColor\" fill-opacity=\"0.4\"></path><path
+ d=\"M21 24C22.6569 24 24 22.6569 24 21C24 19.3431 22.6569 18 21 18C19.3431
+ 18 18 19.3431 18 21C18 22.6569 19.3431 24 21 24Z\" fill=\"currentColor\" fill-opacity=\"0.4\"></path><path
+ d=\"M3 6C4.65685 6 6 4.65685 6 3C6 1.34315 4.65685 0 3 0C1.34315 0 0 1.34315
+ 0 3C0 4.65685 1.34315 6 3 6Z\" fill=\"currentColor\" fill-opacity=\"0.4\"></path><path
+ d=\"M12 6C13.6569 6 15 4.65685 15 3C15 1.34315 13.6569 0 12 0C10.3431 0 9
+ 1.34315 9 3C9 4.65685 10.3431 6 12 6Z\" fill=\"currentColor\" fill-opacity=\"0.4\"></path><path
+ d=\"M21 6C22.6569 6 24 4.65685 24 3C24 1.34315 22.6569 0 21 0C19.3431 0 18
+ 1.34315 18 3C18 4.65685 19.3431 6 21 6Z\" fill=\"currentColor\" fill-opacity=\"0.4\"></path></svg></a></div><div
+ class=\"py-3 sm:col-span-1 md:col-span-2 md:py-8\"><h3 class=\"flex flex-row
+ items-center gap-2.5 px-3 pb-3 font-mdio text-xs font-medium uppercase !tracking-[0.6px]
+ text-gray-700/80\"><span class=\"shrink-0\">Company</span><hr class=\"flex-1
+ border-gray-200\"/></h3><ul class=\"flex flex-col gap-1\"><li class=\"rounded-md
+ p-3 transition-colors duration-200 hover:bg-gray-200\"><a class=\"flex h-[20px]
+ items-center text-sm font-medium text-gray-700 no-underline\" href=\"/company\">About
+ Tailscale</a></li><li class=\"rounded-md p-3 transition-colors duration-200
+ hover:bg-gray-200\"><a class=\"flex h-[20px] items-center text-sm font-medium
+ text-gray-700 no-underline\" href=\"/careers\">Careers</a></li><li class=\"rounded-md
+ p-3 transition-colors duration-200 hover:bg-gray-200\"><a class=\"flex h-[20px]
+ items-center text-sm font-medium text-gray-700 no-underline\" href=\"/press\">Press</a></li><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ class=\"flex h-[20px] items-center text-sm font-medium text-gray-700 no-underline\"
+ href=\"/opensource\">Open Source</a></li></ul></div><div class=\"py-3 sm:col-span-1
+ md:col-span-2 md:py-8\"><h3 class=\"flex flex-row items-center gap-2.5 px-3
+ pb-3 font-mdio text-xs font-medium uppercase !tracking-[0.6px] text-gray-700/80\"><span
+ class=\"shrink-0\">Help & Support</span><hr class=\"flex-1 border-gray-200\"/></h3><ul
+ class=\"flex flex-col gap-1\"><li class=\"rounded-md p-3 transition-colors
+ duration-200 hover:bg-gray-200\"><a class=\"flex h-[20px] items-center text-sm
+ font-medium text-gray-700 no-underline\" href=\"/support\">Support</a></li><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ class=\"flex h-[20px] items-center text-sm font-medium text-gray-700 no-underline\"
+ href=\"/sales\">Sales</a></li><li class=\"rounded-md p-3 transition-colors
+ duration-200 hover:bg-gray-200\"><a class=\"flex h-[20px] items-center text-sm
+ font-medium text-gray-700 no-underline\" href=\"/partnerships\">Partnerships</a></li><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ class=\"flex h-[20px] items-center text-sm font-medium text-gray-700 no-underline\"
+ href=\"/security\">Security</a></li><li class=\"rounded-md p-3 transition-colors
+ duration-200 hover:bg-gray-200\"><a class=\"flex h-[20px] items-center text-sm
+ font-medium text-gray-700 no-underline\" href=\"/changelog\">Changelog</a></li><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ target=\"_blank\" rel=\"noopener noreferrer\" class=\"flex h-[20px] items-center
+ text-sm font-medium text-gray-700 no-underline\" href=\"https://status.tailscale.com\">Tailscale
+ Status</a></li></ul></div><div class=\"py-3 sm:col-span-1 md:col-span-2 md:py-8\"><h3
+ class=\"flex flex-row items-center gap-2.5 px-3 pb-3 font-mdio text-xs font-medium
+ uppercase !tracking-[0.6px] text-gray-700/80\"><span class=\"shrink-0\">Legal</span><hr
+ class=\"flex-1 border-gray-200\"/></h3><ul class=\"flex flex-col gap-1\"><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ class=\"flex h-[20px] items-center text-sm font-medium text-gray-700 no-underline\"
+ href=\"/terms\">Terms of Service</a></li><li class=\"rounded-md p-3 transition-colors
+ duration-200 hover:bg-gray-200\"><a class=\"flex h-[20px] items-center text-sm
+ font-medium text-gray-700 no-underline\" href=\"/privacy\">Privacy Policy</a></li><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ class=\"flex h-[20px] items-center text-sm font-medium text-gray-700 no-underline\"
+ href=\"/privacy-policy#california-notice\">California Notice</a></li><li class=\"rounded-md
+ p-3 transition-colors duration-200 hover:bg-gray-200\"><a class=\"flex h-[20px]
+ items-center text-sm font-medium text-gray-700 no-underline\" href=\"/cookie-notice\">Cookie
+ Notice</a></li><li class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><button
+ type=\"button\" class=\"flex h-[20px] items-center gap-2 text-left text-sm
+ font-medium text-gray-700\">Your privacy choices<img alt=\"Check mark and
+ x on a white and blue pill button\" loading=\"lazy\" width=\"30\" height=\"14\"
+ decoding=\"async\" data-nimg=\"1\" style=\"color:transparent\" src=\"https://cdn.sanity.io/images/w77i7m8x/production/07d853f507039b2489d9818cb6ee7442c1b60e2a-30x14.svg\"/></button></li><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ class=\"flex h-[20px] items-center text-sm font-medium text-gray-700 no-underline\"
+ href=\"/legal\">All Legal</a></li></ul></div><div class=\"py-3 sm:col-span-1
+ md:col-span-2 md:py-8\"><h3 class=\"flex flex-row items-center gap-2.5 px-3
+ pb-3 font-mdio text-xs font-medium uppercase !tracking-[0.6px] text-gray-700/80\"><span
+ class=\"shrink-0\">Social</span><hr class=\"flex-1 border-gray-200\"/></h3><ul
+ class=\"flex flex-col gap-1\"><li class=\"rounded-md p-3 transition-colors
+ duration-200 hover:bg-gray-200\"><a target=\"_blank\" rel=\"noopener noreferrer\"
+ class=\"flex h-[20px] items-center text-sm font-medium text-gray-700 no-underline\"
+ href=\"https://discord.gg/tailscale\">Discord</a></li><li class=\"rounded-md
+ p-3 transition-colors duration-200 hover:bg-gray-200\"><a target=\"_blank\"
+ rel=\"noopener noreferrer\" class=\"flex h-[20px] items-center text-sm font-medium
+ text-gray-700 no-underline\" href=\"https://github.com/tailscale\">GitHub</a></li><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ target=\"_blank\" rel=\"noopener noreferrer\" class=\"flex h-[20px] items-center
+ text-sm font-medium text-gray-700 no-underline\" href=\"https://www.linkedin.com/company/tailscale\">LinkedIn</a></li><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ target=\"_blank\" rel=\"noopener noreferrer\" class=\"flex h-[20px] items-center
+ text-sm font-medium text-gray-700 no-underline\" href=\"https://hachyderm.io/@tailscale\">Mastodon</a></li><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ target=\"_blank\" rel=\"noopener noreferrer\" class=\"flex h-[20px] items-center
+ text-sm font-medium text-gray-700 no-underline\" href=\"https://www.reddit.com/r/Tailscale\">Reddit</a></li><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ target=\"_blank\" rel=\"noopener noreferrer\" class=\"flex h-[20px] items-center
+ text-sm font-medium text-gray-700 no-underline\" href=\"https://www.youtube.com/@Tailscale\">YouTube</a></li><li
+ class=\"rounded-md p-3 transition-colors duration-200 hover:bg-gray-200\"><a
+ target=\"_blank\" rel=\"noopener noreferrer\" class=\"flex h-[20px] items-center
+ text-sm font-medium text-gray-700 no-underline\" href=\"https://twitter.com/tailscale\">X
+ (Twitter)</a></li></ul></div></div><div class=\"mx-3 flex flex-col items-start
+ gap-3 border-gray-200 sm:border-t sm:pt-6 md:mx-8 md:flex-row md:items-center
+ md:border-t-0 md:pt-0\"><span class=\"text-xs text-gray-500\">\xA9 2026 Tailscale
+ Inc.</span><hr class=\"hidden flex-1 self-center border-gray-200 md:inline\"/><span
+ class=\"flex flex-col text-xs text-gray-500 md:block\"><span>Tailscale is
+ a registered trademark of Tailscale Inc.</span><span class=\"hidden md:inline\">
+ | </span><span>WireGuard is a registered trademark of Jason A. Donenfeld</span></span></div></div></footer></div><script
+ id=\"__NEXT_DATA__\" type=\"application/json\">{\"props\":{\"pageProps\":{\"page\":{\"_id\":\"747a1d74-fd7c-43c3-83f5-ae55218823dd\",\"_type\":\"page\",\"cta\":null,\"ctaColor\":\"red\",\"footerStyle\":\"light\",\"headerStyle\":\"light\",\"invertHeader\":null,\"overwriteCTAColor\":true,\"parent\":null,\"removeCTA\":true,\"removeFooter\":null,\"removeHeader\":false,\"sections\":[{\"_type\":\"landingPage\",\"landingPageLayout\":\"error\",\"error\":{\"_type\":\"errorLandingPage\",\"asset\":{\"_type\":\"asset\",\"borderRadius\":\"none\",\"image\":{\"_type\":\"sanityImage\",\"alt\":\"404\",\"asset\":{\"_ref\":\"image-00314739bf834a0d9e0a67cd383f295181584c64-730x730-svg\",\"_type\":\"reference\"}},\"type\":\"image\",\"video\":{\"clickType\":\"watchVideoCaption\",\"mobileStaticAlt\":{\"_type\":\"asset\",\"type\":\"image\",\"video\":{\"clickType\":\"watchVideoCaption\",\"mobileStaticAlt\":{\"_type\":\"asset\",\"type\":\"image\",\"video\":{\"clickType\":\"watchVideoCaption\",\"mobileStaticAlt\":{\"_type\":\"asset\",\"type\":\"image\"},\"playType\":\"click\",\"useMobileStaticAlt\":false}},\"playType\":\"click\",\"useMobileStaticAlt\":false}},\"playType\":\"click\",\"useMobileStaticAlt\":false}},\"label\":{\"icon\":{\"_type\":\"sanityImage\",\"alt\":\"404
+ icon\",\"asset\":{\"_ref\":\"image-c47871628e0abd711d3b18acf850972fff8c7d1c-27x38-svg\",\"_type\":\"reference\"}},\"text\":\"404\"},\"textCard\":{\"_type\":\"textCard\",\"heading\":\"Network
+ (dis)connected\",\"links\":[{\"_key\":\"66dc38668193\",\"button\":{\"_type\":\"button\",\"buttonOptions\":{\"addArrow\":true,\"color\":\"white\"},\"link\":{\"title\":\"Take
+ me back\",\"url\":\"/\"}},\"textLink\":{\"_type\":\"textLink\",\"textLinkOptions\":{\"arrowColor\":\"black\",\"underlineColor\":\"black\"}},\"type\":\"button\"}],\"options\":{\"addSubheading\":false,\"animateOffset\":false,\"contentFontColor\":\"white70\",\"contentFontSize\":\"b18\",\"contentMarginBottom\":\"36\",\"contentMaxWidth\":499,\"hasMobileTextAlignment\":false,\"headingFontColor\":\"white\",\"headingFontSize\":\"80\",\"headingMarginBottom\":\"10\",\"headingMaxWidth\":595,\"headingTag\":\"h2\",\"highlightColor\":\"black\",\"subheadingColor\":\"white\",\"subheadingFontSize\":\"subheading\",\"subheadingMarginBottom\":\"16\",\"subheadingTag\":\"h1\"},\"richContent\":[{\"_key\":\"d980627ead4b\",\"_type\":\"block\",\"children\":[{\"_key\":\"005243496bc10\",\"_type\":\"span\",\"marks\":[],\"text\":\"Don\u2019t
+ worry, you haven\u2019t broken anything. This page has simply disappeared
+ into thin air. Head back to our homepage, and start again.\"}],\"markDefs\":[],\"style\":\"normal\"}]},\"title\":\"404\"}}],\"seo\":{\"indexable\":true,\"seoTitle\":\"404\"},\"slug\":{\"_type\":\"slug\",\"current\":\"404\"},\"themeColor\":null,\"title\":\"404\"},\"global\":{\"announcement\":{\"link\":{\"label\":\"Read
+ the announcement to see what it means for you. \u2192\",\"url\":\"https://tailscale.com/blog/border0-joins-tailscale/?utm_source=announcement-bar\\u0026utm_medium=content\\u0026utm_campaign=border0-announcement\"},\"text\":\"Border0
+ has joined Tailscale!\"},\"footer\":{\"_createdAt\":\"2023-10-06T14:44:29Z\",\"_id\":\"422b4abf-6e3f-4213-ab94-a03dd444be3d\",\"_rev\":\"cvyAZxF5iE3rKFZlZADQj6\",\"_system\":{\"base\":{\"id\":\"422b4abf-6e3f-4213-ab94-a03dd444be3d\",\"rev\":\"QO1lT9vtV5kYGc8zKtne7R\"}},\"_type\":\"footer\",\"_updatedAt\":\"2025-11-12T18:00:18Z\",\"copyrightContent\":\"Tailscale
+ Inc. All rights reserved. Tailscale is a registered trademark of Tailscale
+ Inc.\",\"cta\":{\"asset\":{\"_type\":\"asset\",\"image\":{\"_type\":\"sanityImage\",\"alt\":\"cta
+ phone\",\"asset\":{\"_ref\":\"image-b715b4ca5e2577da60f0d529a4a9bc2ad4cadf59-362x567-svg\",\"_type\":\"reference\"}},\"type\":\"image\"},\"ctaButton\":{\"_type\":\"button\",\"buttonOptions\":{\"color\":\"white\"},\"link\":{\"title\":\"Get
+ started\",\"url\":\"/get-started\"}},\"darkLogoGrid\":[{\"_key\":\"fb360d1bc6c4\",\"logo\":{\"_type\":\"sanityImage\",\"alt\":\"mercury\",\"asset\":{\"_ref\":\"image-a1fb7441ec6ea5254d0f14119dbe0abf5c822f9f-199x81-svg\",\"_type\":\"reference\"}}},{\"_key\":\"a24139987731\",\"logo\":{\"_type\":\"sanityImage\",\"alt\":\"instacart\",\"asset\":{\"_ref\":\"image-62410277e3cd5df52c9b59e787ae52a5a2699580-199x81-svg\",\"_type\":\"reference\"}}},{\"_key\":\"0fa57e2eebee\",\"logo\":{\"_type\":\"sanityImage\",\"alt\":\"Retool\",\"asset\":{\"_ref\":\"image-80654c9d97220caec3e35ba29d3e7439a03d482a-199x82-svg\",\"_type\":\"reference\"}}},{\"_key\":\"b47251ca28bd\",\"logo\":{\"_type\":\"sanityImage\",\"alt\":\"duolingo\",\"asset\":{\"_ref\":\"image-9b799915a326b1b78decc95e6ce251b87111f2bf-199x81-svg\",\"_type\":\"reference\"}}},{\"_key\":\"7bf06e2e5305\",\"logo\":{\"_type\":\"sanityImage\",\"alt\":\"Hugging
+ Face\",\"asset\":{\"_ref\":\"image-b36780abd0594e34b52e74176a6b61811bbed602-199x82-svg\",\"_type\":\"reference\"}}}],\"heading\":\"Try
+ Tailscale for |free|\",\"logoGrid\":[{\"_key\":\"a3a9b2012378\",\"logo\":{\"_type\":\"sanityImage\",\"alt\":\"mercury\",\"asset\":{\"_ref\":\"image-459a7a8492910eeb22f22bb8d4c0f864b0bae25f-199x81-svg\",\"_type\":\"reference\"}}},{\"_key\":\"993b75d39e13\",\"logo\":{\"_type\":\"sanityImage\",\"alt\":\"instacrt\",\"asset\":{\"_ref\":\"image-7d127f4bb62a408b056328349f291857df6251b3-199x81-svg\",\"_type\":\"reference\"}}},{\"_key\":\"8449f10eb5c7\",\"logo\":{\"_type\":\"sanityImage\",\"alt\":\"Retool\",\"asset\":{\"_ref\":\"image-e9579b00087d7896e9cb750f4eb39f2c11ed11b8-199x82-svg\",\"_type\":\"reference\"}}},{\"_key\":\"3ab303288a39\",\"logo\":{\"_type\":\"sanityImage\",\"alt\":\"duolingo\",\"asset\":{\"_ref\":\"image-7958bf3d43a30e661ca74cf0510f250d9b99ecef-199x81-svg\",\"_type\":\"reference\"}}},{\"_key\":\"5e630c781c8a\",\"logo\":{\"_type\":\"sanityImage\",\"alt\":\"Hugging
+ Face\",\"asset\":{\"_ref\":\"image-68e2e5024898bcd6f6d142e0306dc7564787e1d7-199x82-svg\",\"_type\":\"reference\"}}}],\"secondaryCta\":{\"heading\":\"Schedule
+ a demo\",\"link\":{\"title\":\"Contact sales\",\"url\":\"/contact/sales\"}},\"textCard\":{\"_type\":\"textCard\",\"heading\":\"Try
+ Tailscale for |free|\",\"links\":[{\"_key\":\"dc04d805f7e0\",\"button\":{\"_type\":\"button\",\"buttonOptions\":{\"color\":\"white\"},\"link\":{\"title\":\"Get
+ started\",\"url\":\"https://login.tailscale.com/start\"}},\"textLink\":{\"_type\":\"textLink\",\"textLinkOptions\":{\"arrowColor\":\"black\",\"underlineColor\":\"black\"}},\"type\":\"button\"}],\"options\":{\"contentFontColor\":\"white\",\"hasMobileTextAlignment\":false,\"headingFontColor\":\"white\",\"headingFontSize\":\"h3\",\"headingMarginBottom\":\"30\",\"headingMaxWidth\":292,\"highlightColor\":\"blue-3\",\"sectionAlignment\":\"left\"}}},\"footerNav\":[{\"_key\":\"05f3fa61c972\",\"heading\":\"Product\",\"links\":[{\"_key\":\"30386cf08177\",\"title\":\"How
+ it works\",\"url\":\"/blog/how-tailscale-works/\"},{\"_key\":\"45dec9531713\",\"title\":\"Pricing\",\"url\":\"/pricing\"},{\"_key\":\"e6f4d8daff21\",\"title\":\"Integrations\",\"url\":\"/integrations\"},{\"_key\":\"d4f7875a767f\",\"title\":\"Features\",\"url\":\"/features\"},{\"_key\":\"64846fcdaf3b\",\"title\":\"Compare
+ Tailscale\",\"url\":\"/compare\"}]},{\"_key\":\"7870d03d9802\",\"heading\":\"Use
+ Cases\",\"links\":[{\"_key\":\"7b4858603fc7\",\"title\":\"Business VPN\",\"url\":\"/use-cases/business-vpn\"},{\"_key\":\"06fbf46e9354\",\"title\":\"CI/CD\",\"url\":\"/use-cases/ci-cd\"},{\"_key\":\"602f747f5b2a\",\"title\":\"Infra
+ Access\",\"url\":\"/use-cases/infrastructure-access\"},{\"_key\":\"8660f39ec574\",\"title\":\"Cloud
+ Connectivity\",\"url\":\"/use-cases/cloud-connectivity\"},{\"_key\":\"ab3e69241df2\",\"title\":\"Zero
+ Trust Networking\",\"url\":\"/use-cases/zero-trust-networking\"},{\"_key\":\"b79f544a8266\",\"title\":\"Homelab\",\"url\":\"/use-cases/homelab\"}]},{\"_key\":\"2e262725243d\",\"heading\":\"Resources\",\"links\":[{\"_key\":\"b5ad8866742c\",\"title\":\"Blog\",\"url\":\"/blog\"},{\"_key\":\"21869f26f11b\",\"title\":\"Events
+ \\u0026 Webinars\",\"url\":\"/events-webinars\"},{\"_key\":\"c844ea072844\",\"title\":\"Partnerships\",\"url\":\"/partnerships\"}]},{\"_key\":\"a1e16018d519\",\"heading\":\"Company\",\"links\":[{\"_key\":\"8cc3fedb5b31\",\"title\":\"Company\",\"url\":\"/company\"},{\"_key\":\"e69d139c2c7c\",\"title\":\"Careers\",\"url\":\"/careers\"},{\"_key\":\"ad370d7ab2c1\",\"title\":\"Press\",\"url\":\"/press\"}]},{\"_key\":\"b25bd2c7203e\",\"heading\":\"Help
+ \\u0026 Support\",\"links\":[{\"_key\":\"f7d6ef6a99c6\",\"title\":\"Support\",\"url\":\"/contact/support\"},{\"_key\":\"18077954da8f455140153a58c74e53ba\",\"title\":\"Sales\",\"url\":\"/contact/sales\"},{\"_key\":\"3b91a6bb3d6b\",\"title\":\"Security\",\"url\":\"/security\"},{\"_key\":\"9d3e837341e2\",\"title\":\"Legal\",\"url\":\"/legal\"},{\"_key\":\"a69304fe5b80\",\"title\":\"Open
+ Source\",\"url\":\"/opensource\"},{\"_key\":\"a02943ca7fdd\",\"title\":\"Changelog\",\"url\":\"/changelog\"},{\"_key\":\"94a676559b98\",\"title\":\"Tailscale
+ Status\",\"url\":\"https://status.tailscale.com/\"}]},{\"_key\":\"0bdaf34fbe61\",\"heading\":\"Learn\",\"links\":[{\"_key\":\"6c45141fcc65\",\"title\":\"SSH
+ keys\",\"url\":\"/learn/generate-ssh-keys/\"},{\"_key\":\"86c070f995c4\",\"title\":\"Docker
+ SSH\",\"url\":\"/learn/ssh-into-docker-container/\"},{\"_key\":\"22e6d051e763\",\"title\":\"NAT
+ Traversal\",\"url\":\"/blog/how-nat-traversal-works/\"},{\"_key\":\"4e51a8a4f0a7\",\"title\":\"MagicDNS\",\"url\":\"/blog/2021-09-private-dns-with-magicdns/\"},{\"_key\":\"f8f8893085b3\",\"title\":\"PAM\",\"url\":\"/learn/privileged-access-management/\"},{\"_key\":\"e7fdb19bd312\",\"title\":\"All
+ articles\",\"url\":\"/learn\"}]}],\"legalContent\":\"WireGuard is a registered
+ trademark of Jason A. Donenfeld.\",\"legalNav\":[{\"_key\":\"b3b1d8dfddea\",\"title\":\"Terms
+ of Service\",\"url\":\"/terms\"},{\"_key\":\"3e22b7802445\",\"title\":\"Privacy
+ Policy\",\"url\":\"/privacy-policy\"},{\"_key\":\"78a1108d49e9\",\"title\":\"California
+ Notice\",\"url\":\"https://tailscale.com/privacy-policy#california-notice\"},{\"_key\":\"9d3370545d40\",\"title\":\"Cookie
+ Notice\",\"url\":\"https://tailscale.com/cookie-notice\"}],\"title\":\"Production
+ Footer\"},\"globalOptions\":null,\"header\":{\"_createdAt\":\"2023-10-06T12:21:23Z\",\"_id\":\"7797109d-2dc4-4a75-b5a3-b1019c33212f\",\"_rev\":\"j2fyDG1GUVWvNQkvjTn5zS\",\"_system\":{\"base\":{\"id\":\"7797109d-2dc4-4a75-b5a3-b1019c33212f\",\"rev\":\"5FsRtrxmPymeiFerrOYlEk\"}},\"_type\":\"header\",\"_updatedAt\":\"2026-02-02T22:44:03Z\",\"button\":{\"_type\":\"button\",\"buttonOptions\":{\"color\":\"black\"},\"link\":{\"title\":\"Get
+ started - it's free!\",\"url\":\"https://login.tailscale.com/start\"}},\"links\":[{\"_key\":\"157b4ad1150d\",\"title\":\"Download\",\"url\":\"/download\"},{\"_key\":\"f00209e74f6b\",\"title\":\"Log
+ in\",\"url\":\"https://login.tailscale.com/welcome\"}],\"menu\":[{\"_key\":\"95381f81d527\",\"hasSubmenu\":true,\"submenu\":{\"product\":{\"leftCol\":{\"topNav\":{\"heading\":\"Meet
+ Tailscale\",\"links\":[{\"_key\":\"5495d201056a\",\"icon\":{\"_type\":\"sanityImage\",\"alt\":\"icon\"},\"link\":\"/blog/how-tailscale-works/\",\"title\":\"How
+ it works\"},{\"_key\":\"dc9cde7ff83cb94cfc98ff29bdcd0997\",\"icon\":{\"_type\":\"sanityImage\",\"alt\":\"icon\"},\"link\":\"/why-tailscale\",\"title\":\"Why
+ Tailscale\"},{\"_key\":\"5d88e3ffcc6b\",\"icon\":{\"_type\":\"sanityImage\",\"alt\":\"WireGuard\xAE\"},\"link\":\"/wireguard-vpn\",\"title\":\"WireGuard\xAE
+ for Enterprises\"},{\"_key\":\"435de37ddd5f\",\"icon\":{\"_type\":\"sanityImage\",\"alt\":\"Bring
+ Tailscale to Work\"},\"link\":\"/bring-tailscale-to-work\",\"title\":\"Bring
+ Tailscale to Work\"}]}},\"rightCol\":{\"nav\":{\"heading\":\"Explore\",\"links\":[{\"_key\":\"c653da519dfb\",\"link\":\"/integrations\",\"title\":\"Integrations\"},{\"_key\":\"a878da5fa54c\",\"link\":\"/features\",\"title\":\"Features\"},{\"_key\":\"adda698ed879\",\"link\":\"/compare\",\"title\":\"Compare
+ Tailscale\"},{\"_key\":\"3341bd9805df\",\"link\":\"/community/community-projects\",\"title\":\"Community
+ Projects\"},{\"_key\":\"b57369965809\",\"link\":\"/partnerships\",\"title\":\"Partnerships\"}]}}},\"submenuType\":\"product\"},\"title\":\"Product\"},{\"_key\":\"a7062f1924df\",\"hasSubmenu\":true,\"submenu\":{\"product\":{\"leftCol\":{\"topNav\":{\"heading\":\"By
+ use-case\",\"links\":[{\"_key\":\"6fc65e9fe1c6\",\"link\":\"/use-cases/business-vpn\",\"title\":\"Business
+ VPN\"},{\"_key\":\"193eaaa0cef8\",\"link\":\"/use-cases/ci-cd\",\"title\":\"CI/CD\"},{\"_key\":\"c59997229dde\",\"link\":\"/use-cases/infrastructure-access\",\"title\":\"Infra
+ Access\"},{\"_key\":\"05cadfcf3e65b04708a9d88060f68f9e\",\"link\":\"/use-cases/cloud-connectivity\",\"title\":\"Cloud
+ Connectivity\"},{\"_key\":\"6a363d694952\",\"link\":\"/use-cases/zero-trust-networking\",\"title\":\"Zero
+ Trust Networking\"},{\"_key\":\"d99d14013ab3\",\"link\":\"/use-cases/homelab\",\"title\":\"Homelab\"},{\"_key\":\"b049143e7a1c\",\"link\":\"/use-cases/securing-ai\",\"title\":\"Securing
+ AI\"}]}},\"rightCol\":{\"nav\":{\"heading\":\"By role\",\"links\":[{\"_key\":\"502a00f49baf\",\"link\":\"/solutions/devops\",\"title\":\"DevOps\"},{\"_key\":\"0fe4c0d6fa83\",\"link\":\"/solutions/it\",\"title\":\"IT\"},{\"_key\":\"026f30b876a7\",\"link\":\"/solutions/security\",\"title\":\"Security\"}]}}},\"submenuType\":\"product\"},\"title\":\"Solutions\"},{\"_key\":\"fd055b16290df04c6012d0d33c2fad13\",\"hasSubmenu\":false,\"link\":\"/enterprise\",\"submenu\":{\"submenuType\":\"product\"},\"title\":\"Enterprise\"},{\"_key\":\"b595975539c7407a7ed4510edd549223\",\"hasSubmenu\":false,\"link\":\"/customers\",\"submenu\":{\"product\":{\"leftCol\":{\"topNav\":{\"heading\":\"Nav
+ heading here\",\"links\":[{\"_key\":\"2d22491d8262\",\"description\":\"How
+ Cribl Enables Secure Work From Anywhere with Tailscale\",\"icon\":{\"_type\":\"sanityImage\",\"alt\":\"Alt
+ text \",\"asset\":{\"_ref\":\"image-a06dc612b1e3e4f4df53a72030002600639a8738-300x120-png\",\"_type\":\"reference\"}},\"link\":\"https://tailscale.com/customers\",\"title\":\"Title
+ here\"}]}}},\"resources\":{\"topNav\":[{\"_key\":\"61d0f0cb130e\",\"description\":\"How
+ Cribl Enables Secure Work From Anywhere with Tailscale\",\"heading\":\"Cribl\"},{\"_key\":\"712684d509a6a6ea07ab9401bdb23f8f\",\"description\":\"How
+ Cribl Enables Secure Work From Anywhere with Tailscale\",\"heading\":\"Cribl\"},{\"_key\":\"ceac3f234a3a6923a671af91772b7e8b\",\"description\":\"How
+ Cribl Enables Secure Work From Anywhere with Tailscale\",\"heading\":\"Cribl\"}]},\"submenuType\":\"product\"},\"title\":\"Customers\"},{\"_key\":\"f06fabeb084c\",\"hasSubmenu\":false,\"link\":\"/docs\",\"submenu\":{\"submenuType\":\"product\"},\"title\":\"Docs\"},{\"_key\":\"f2537b6fa068\",\"hasSubmenu\":false,\"link\":\"/blog\",\"submenu\":{\"submenuType\":\"product\"},\"title\":\"Blog\"},{\"_key\":\"e1b7b44dc091\",\"hasSubmenu\":false,\"link\":\"/pricing\",\"submenu\":{\"submenuType\":\"product\"},\"title\":\"Pricing\"}],\"title\":\"Production
+ Header\"},\"legal\":null,\"newsBar\":null,\"redirects\":[{\"_key\":\"8b0a3ebcf822\",\"destination\":\"/wireguard-vpn\",\"source\":\"/wireguard\"}],\"seo\":{\"ogImage\":{\"_type\":\"image\",\"asset\":{\"_ref\":\"image-8e0455b2d9b33c6151016afdf2ea81d7623c2f04-1200x628-png\",\"_type\":\"reference\"}}},\"socials\":null,\"newHeader\":{\"links\":[{\"feature\":{\"buttons\":[{\"_key\":\"eda43f90569a\",\"_type\":\"buttonsMember\",\"openInNewTab\":false,\"text\":\"Click
+ here\",\"url\":\"https://tailscale.com/use-cases/securing-ai\"}],\"contentText\":\"Unified
+ AI governance for AI agents and users.\",\"image\":{\"alt\":\"aperture dashboard\",\"asset\":{\"_createdAt\":\"2026-03-18T00:32:42Z\",\"_id\":\"image-7fbf14ac75bdf258eeff9efb2a2d5bbbf2e87098-266x342-svg\",\"_rev\":\"9SdknGxd6abfl1HH8tyFpw\",\"_type\":\"sanity.imageAsset\",\"_updatedAt\":\"2026-03-18T00:32:42Z\",\"assetId\":\"7fbf14ac75bdf258eeff9efb2a2d5bbbf2e87098\",\"extension\":\"svg\",\"metadata\":{\"_type\":\"sanity.imageMetadata\",\"blurHash\":\"d,Ju4Lxuxuxut7RkWBay~qRjRjWBM{ofofj[xuoJaeWB\",\"dimensions\":{\"_type\":\"sanity.imageDimensions\",\"aspectRatio\":0.7777777777777778,\"height\":342,\"width\":266},\"hasAlpha\":true,\"isOpaque\":true,\"lqip\":\"data:image/jpeg;base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAaABQDASIAAhEBAxEB/8QAGQAAAwADAAAAAAAAAAAAAAAAAAcIAgUG/8QAKRAAAQMCBQIGAwAAAAAAAAAAAQACAwUGBAcREiExUxUiVXGRkyNzkv/EABYBAQEBAAAAAAAAAAAAAAAAAAABAv/EABkRAQEAAwEAAAAAAAAAAAAAAAABAhExEv/aAAwDAQACEQMRAD8AZVWuy3qViJIMZNGyWMgObpyNVtoavbErA7xintBGvMgU751fjvuoNeCBo3gcHol/odu7z6e61l5vITc6sw1O2PWqb9gQoz2uPI36e6FldnlnFlrdFfvbG46kU4T4WQN2v3gdAuIOTl+Bm00dmn7AqrEsncf8lZGWTuP/AKKglA5O336S37AhVYZZO4/5KEH/2Q==\",\"palette\":{\"_type\":\"sanity.imagePalette\",\"darkMuted\":{\"_type\":\"sanity.imagePaletteSwatch\",\"background\":\"#2f3651\",\"foreground\":\"#fff\",\"population\":0.27,\"title\":\"#fff\"},\"darkVibrant\":{\"_type\":\"sanity.imagePaletteSwatch\",\"background\":\"#2a386e\",\"foreground\":\"#fff\",\"population\":0.57,\"title\":\"#fff\"},\"dominant\":{\"_type\":\"sanity.imagePaletteSwatch\",\"background\":\"#6490e7\",\"foreground\":\"#fff\",\"population\":1.21,\"title\":\"#fff\"},\"lightMuted\":{\"_type\":\"sanity.imagePaletteSwatch\",\"background\":\"#dad1c4\",\"foreground\":\"#000\",\"population\":0.02,\"title\":\"#000\"},\"lightVibrant\":{\"_type\":\"sanity.imagePaletteSwatch\",\"background\":\"#d3d9f1\",\"foreground\":\"#000\",\"population\":0.23,\"title\":\"#000\"},\"muted\":{\"_type\":\"sanity.imagePaletteSwatch\",\"background\":\"#7e7e7e\",\"foreground\":\"#fff\",\"population\":1.04,\"title\":\"#fff\"},\"vibrant\":{\"_type\":\"sanity.imagePaletteSwatch\",\"background\":\"#6490e7\",\"foreground\":\"#fff\",\"population\":1.21,\"title\":\"#fff\"}}},\"mimeType\":\"image/svg+xml\",\"originalFilename\":\"nav-platform-cta-aperture-2.svg\",\"path\":\"images/w77i7m8x/production-v2/7fbf14ac75bdf258eeff9efb2a2d5bbbf2e87098-266x342.svg\",\"sha1hash\":\"7fbf14ac75bdf258eeff9efb2a2d5bbbf2e87098\",\"size\":982603,\"uploadId\":\"zOFizzW077I05H6oARSzfK7gcjwqQZCn\",\"url\":\"https://cdn.sanity.io/images/w77i7m8x/production-v2/7fbf14ac75bdf258eeff9efb2a2d5bbbf2e87098-266x342.svg\"}},\"title\":\"Aperture
+ by Tailscale\"},\"label\":\"Platform\",\"tray\":{\"buttons\":[{\"_key\":\"3388b94d369d\",\"_type\":\"buttonsMember\",\"openInNewTab\":false,\"text\":\"Read
+ more\",\"url\":\"https://tailscale.com/winter-update-week-26\"}],\"contentText\":\"The
+ latest improvements on Aperture, Peer Relays, Services, and more.\",\"title\":\"Tailscale
+ Winter Update\"}},{\"feature\":null,\"label\":\"Customer Stories\",\"tray\":null},{\"feature\":null,\"label\":\"Community\",\"tray\":{\"buttons\":[{\"_key\":\"06c200585c0f\",\"_type\":\"buttonsMember\",\"openInNewTab\":false,\"text\":\"Learn
+ more\",\"url\":\"https://tailscale.com/tailscaleup\"}],\"contentText\":\"Tailscale\u2019s
+ conference for engineering, security, and IT leaders.\",\"title\":\"Join us
+ at TailscaleUp\"}},{\"feature\":null,\"label\":\"Partnerships\",\"tray\":null}],\"title\":\"Production
+ Header\"},\"newAnnouncement\":{\"icon\":null,\"link\":{\"label\":\"Read more
+ \u2192\",\"url\":\"https://tailscale.com/blog/aperture-self-serve/?utm_source=announcement-bar\\u0026utm_medium=content\\u0026utm_campaign=aperture-self-serve\"},\"text\":\"APERTURE
+ IS NOW AVAILABLE - START BUILDING WITH AI SAFELY IN MINUTES, NO WAITLIST.\",\"themeColor\":\"purple\",\"title\":\"Production
+ Announcement\"}}},\"__N_SSG\":true},\"page\":\"/404\",\"query\":{},\"buildId\":\"yXNXD6wKMziT90Wz_CAoa\",\"isFallback\":false,\"gsp\":true,\"scriptLoader\":[]}</script></body></html>"
+ headers:
+ Age:
+ - '62'
+ Cache-Control:
+ - public, max-age=0, must-revalidate
+ Connection:
+ - close
+ Content-Length:
+ - '78615'
+ Content-Security-Policy-Report-Only:
+ - "default-src 'self' wwwv2.tailscale.com;\tscript-src 'self' wwwv2.tailscale.com
+ bat.bing.com cdn.rudderlabs.com www.google-analytics.com www.googletagmanager.com
+ www.google.com *.mutinycdn.com js.hs-scripts.com js.hs-banner.com js.hubspot.com
+ js.hs-analytics.com *.hsforms.net unpkg.com snap.licdn.com www.redditstatic.com
+ https://bwa.marketplace.awsstatic.com widget.kapa.ai;\tconnect-src 'self'
+ wwwv2.tailscale.com login.tailscale.com bat.bing.com *.mutinyhq.io *.mutinycdn.com
+ analytics.google.com www.google-analytics.com cdn.sanity.io unpkg.com *.rudderstack.com
+ *.hubspot.com www.redditstatic.com pixel-config.reddit.com px.ads.linkedin.com
+ https://medley.prod.irtysh.dubai.aws.dev proxy.kapa.ai kapa-widget-proxy-la7dkmplpq-uc.a.run.app
+ metrics.kapa.ai;\timg-src 'self' wwwv2.tailscale.com cdn.sanity.io lh3.googleusercontent.com
+ www.google-analytics.com *.hsforms.com alb.reddit.com px.ads.linkedin.com
+ bat.bing.com track.hubspot.com;\tframe-ancestors 'none';\tform-action 'self'
+ wwwv2.tailscale.com;\tbase-uri 'self' wwwv2.tailscale.com;\tblock-all-mixed-content;\tobject-src
+ 'self' wwwv2.tailscale.com;\treport-to csp-endpoint;\treport-uri https://login.tailscale.com/csp-report;"
+ Content-Type:
+ - text/html; charset=utf-8
+ Date:
+ - Sat, 28 Mar 2026 11:27:17 GMT
+ Etag:
+ - '"yw4bnmoqu71omt"'
+ Reporting-Endpoints:
+ - csp-endpoint="https://login.tailscale.com/csp-report"
+ Server:
+ - Vercel
+ Strict-Transport-Security:
+ - max-age=63072000
+ X-Matched-Path:
+ - /[...slug]
+ X-Vercel-Cache:
+ - HIT
+ X-Vercel-Id:
+ - lhr1::iad1::vc7vg-1774697299824-ba5a8b5f5776
+ status:
+ code: 404
+ message: Not Found
+version: 1
tests/stubs/vcr.cassette.pyi (20) → tests/stubs/vcr.cassette.pyi (31)
diff --git a/tests/stubs/vcr.cassette.pyi b/tests/stubs/vcr.cassette.pyi
index 5c8ea15..4da50ac 100644
--- a/tests/stubs/vcr.cassette.pyi
+++ b/tests/stubs/vcr.cassette.pyi
@@ -1 +1,2 @@
-class Cassette: ...
+class Cassette:
+ _path: str
tests/stubs/vcr.pyi (0) → tests/stubs/vcr.pyi (342)
diff --git a/tests/stubs/vcr.pyi b/tests/stubs/vcr.pyi
new file mode 100644
index 0000000..7df326e
--- /dev/null
+++ b/tests/stubs/vcr.pyi
@@ -0,0 +1,11 @@
+import contextlib
+
+from vcr.cassette import Cassette
+
+def use_cassette(
+ cassette_name: str,
+ cassette_library_dir: str,
+ decode_compressed_response: bool,
+ filter_query_parameters: list[tuple[str, str]] | None = None,
+ filter_headers: list[tuple[str, str]] | None = None,
+) -> contextlib.AbstractContextManager[Cassette]: ...
tests/test_cassettes.py (0) → tests/test_cassettes.py (3304)
diff --git a/tests/test_cassettes.py b/tests/test_cassettes.py
new file mode 100644
index 0000000..6a790a6
--- /dev/null
+++ b/tests/test_cassettes.py
@@ -0,0 +1,100 @@
+"""
+Tests for `cassettes`.
+"""
+
+import pytest
+from vcr.cassette import Cassette
+
+from cassettes import get_cassette_name
+
+
+def test_creates_cassette(cassette_name: str) -> None:
+ """
+ The filename of a VCR cassette is the test name, plus a YAML extension.
+ """
+ assert cassette_name == "test_creates_cassette.yml"
+
+
+@pytest.mark.parametrize(
+ ["expected_cassette_name"],
+ [
+ pytest.param("test_creates_parametrized_cassette[test1].yml", id="test1"),
+ pytest.param("test_creates_parametrized_cassette[test2].yml", id="test2"),
+ pytest.param("test_creates_parametrized_cassette[test3].yml", id="test3"),
+ ],
+)
+def test_creates_parametrized_cassette(
+ cassette_name: str, expected_cassette_name: str
+) -> None:
+ """
+ In a parametrized test, the filename of the VCR cassette includes
+ the test case name in square brackets.
+ """
+ assert cassette_name == expected_cassette_name
+
+
+class TestCassetteNameInClass:
+ """
+ TestCassetteNameInClass exists so the next test can run inside
+ a class, simulating how tests are often organised in "real" test suites.
+ """
+
+ def test_prefixes_class_name_to_cassette(self, cassette_name: str) -> None:
+ """
+ In a test in a class, the filename of the VCR cassette includes
+ the class name as a prefix.
+ """
+ assert (
+ cassette_name
+ == "TestCassetteNameInClass.test_prefixes_class_name_to_cassette.yml"
+ )
+
+ @pytest.mark.parametrize(
+ ["expected_cassette_name"],
+ [
+ pytest.param(
+ "TestCassetteNameInClass.test_prefixes_name_with_parametrized_cassette[test1].yml",
+ id="test1",
+ ),
+ pytest.param(
+ "TestCassetteNameInClass.test_prefixes_name_with_parametrized_cassette[test2].yml",
+ id="test2",
+ ),
+ pytest.param(
+ "TestCassetteNameInClass.test_prefixes_name_with_parametrized_cassette[test3].yml",
+ id="test3",
+ ),
+ pytest.param(
+ "TestCassetteNameInClass.test_prefixes_name_with_parametrized_cassette[test.name.with.periods].yml",
+ id="test.name.with.periods",
+ ),
+ ],
+ )
+ def test_prefixes_name_with_parametrized_cassette(
+ self, cassette_name: str, expected_cassette_name: str
+ ) -> None:
+ """
+ In a parametrized test in a class, the filename of the
+ VCR cassette includes the class name as a prefix and the
+ test case name as a suffix.
+ """
+ assert cassette_name == expected_cassette_name
+
+
+@pytest.mark.parametrize("url", ["https://example.com"])
+def test_throws_if_bad_cassette_name(url: str, request: pytest.FixtureRequest) -> None:
+ """
+ Trying to use a URL as a VCR cassette name throws a ``ValueError``.
+ """
+ with pytest.raises(ValueError, match="Illegal characters in VCR cassette name"):
+ get_cassette_name(request)
+
+
+def test_creates_cassette_in_fixture_dir(vcr_cassette: Cassette) -> None:
+ """
+ The VCR cassette is created in the ``tests/fixtures/cassettes`` directory.
+ """
+ assert (
+ vcr_cassette._path
+ == "tests/fixtures/cassettes/test_creates_cassette_in_fixture_dir.yml"
+ )
tests/test_urls.py (4359) → tests/test_urls.py (4358)
diff --git a/tests/test_urls.py b/tests/test_urls.py
index b33f807..4fdb2bd 100644
--- a/tests/test_urls.py
+++ b/tests/test_urls.py
@@ -123,7 +123,7 @@ class TestIsMastodonHost:
# These are regular Internet websites which don't expose
# the /.well-known/nodeinfo endpoint
"example.com",
- "alexwlchan.net",
+ "tailscale.com",
#
# PeerTube exposes /.well-known/nodeinfo, but it's running
# different software.