{"slug":"tailscale-ssh-session-recording","title":"Tailscale SSH session recording","tags":["tailscale"],"agent_summary":"Last validated: Jan 5, 2026","trigger_phrases":[],"runnable":false,"markdown":"\r\n# Tailscale SSH session recording\r\n\r\nLast validated: Jan 5, 2026\r\n\r\nTailscale SSH session recording lets you stream logs of [Tailscale SSH](https://tailscale.com/docs/features/tailscale-ssh) sessions to another node in your tailnet. These recordings are encrypted end-to-end just like all other Tailscale traffic.\r\n\r\nTailscale SSH session recordingis currently [in beta](https://tailscale.com/docs/reference/tailscale-release-stages#beta). To try it, follow the steps below to enable it for your network using Tailscale v1.40.1 or later.\r\n\r\nTailscale SSH session recordingis available for [the Personal and Enterprise plans](https://tailscale.com/pricing).\r\n\r\nThe Tailscale session recorder now supports recording all [Kubernetes sessions and API requests](https://tailscale.com/docs/features/kubernetes-operator/how-to/session-recording).\r\n\r\n## [How it works](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#how-it-works)\r\n\r\nTailscale SSH session recording lets you stream recordings of Tailscale SSH sessions from the destination node to a recorder node in your tailnet. The recorder is a [`tsnet` binary](https://tailscale.com/blog/tsnet-virtual-private-services), which you deploy to a machine or [Kubernetes cluster](https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder) you manage. Logs are streamed over your tailnet to your node, so they are end-to-end encrypted and not visible to Tailscale. The recorder can either store the log files on the host filesystem, or export them to Amazon S3 or another S3-compatible storage service.\r\n\r\nTailscale SSH session recording captures terminal sessions in [`asciinema`](https://asciinema.org/) format. These recordings are newline-delimited JSON files that can be searched as text, and replayed from the recorder node's optional web UI or the `asciinema` CLI.\r\n\r\nSession recording captures all terminal output, meaning any text that appears on the terminal screen during a Tailscale SSH session. This could include sensitive data. No keystrokes are captured.\r\n\r\nWhen using binary protocols like older versions of `scp`, session recording will capture the transmitted binary data. This could include sensitive data.\r\n\r\n## [Set up session recording for Tailscale SSH](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#set-up-session-recording-for-tailscale-ssh)\r\n\r\n### [Prerequisites](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#prerequisites)\r\n\r\nSession recording is only available for SSH connections over Tailscale SSH.\r\n\r\nYou need to be an [Owner, Admin, or Network admin](https://tailscale.com/docs/reference/user-roles)\r\nof a tailnet to set up session recording.\r\n\r\nTo enable session recording, you must:\r\n\r\n- Ensure that Tailscale SSH is properly configured in your tailnet.\r\n- Deploy a recorder node in your tailnet.\r\n- Turn on session recording in your tailnet policy file by adding a `recorder:[\"tag:<tag-name>\"]` field to each SSH policy entry. SSH sessions permitted by that stanza will be streamed to any recorder with the named tag.\r\n\r\nThe recorder node binary will run on any platform as a Docker container. Because the binary uses [`tsnet`](https://tailscale.com/blog/tsnet-virtual-private-services) to join your tailnet as a node, the Docker host does not need to be a member of your tailnet. The application listens on port `80` to receive session recordings, and provides an optional, built-in web application for viewing session recordings at port `443`.\r\n\r\n### [Create a tag and auth key for session recorders](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#create-a-tag-and-auth-key-for-session-recorders)\r\n\r\nA recorder node must be associated with a tag. We recommend that you create a new [tag](https://tailscale.com/docs/features/tags) specifically to use with SSH session recording. You can do this by visiting the [Access controls](https://login.tailscale.com/admin/acls) page of the admin console and defining an owner of the tag in access control policies.\r\n\r\n```json\r\n// Example tag for SSH session recorders\r\n\"tagOwners\": {\r\n  \"tag:session-recorder\": [\\\r\n    \"<tag-owner>\",\\\r\n  ],\r\n}\r\n```\r\n\r\nNext, visit the [Keys](https://login.tailscale.com/admin/settings/keys) page of the admin console to create an auth key. Assign the new tag you created to this auth key. If your tailnet has device approval enabled, use a pre-approved key. Be sure to copy this auth key to your clipboard.\r\n\r\nOn the host where you will deploy the recorder node container, set the `TS_AUTHKEY` environment variable to the auth key. This is only necessary the first time you start the recorder application.\r\n\r\n```markup\r\nexport TS_AUTHKEY=<your-auth-key>\r\n```\r\n\r\n### [Deploy a recorder node](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#deploy-a-recorder-node)\r\n\r\nThe recorder is provided as a Docker container, and you can find it on [Tailscale's DockerHub profile](https://hub.docker.com/r/tailscale/tsrecorder/tags).\r\n\r\nIf you use Tailscale's Kubernetes operator, you can also deploy the recorder using the [`Recorder` Custom Resource](https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder).\r\n\r\n```shell\r\ndocker pull tailscale/tsrecorder:stable\r\n```\r\n\r\nTo run the container and save recordings to the Docker host's filesystem:\r\n\r\n```shell\r\ndocker run --name tsrecorder --rm -it \\\r\n  -e TS_AUTHKEY=$TS_AUTHKEY \\\r\n  -v $HOME/tsrecorder:/data \\\r\n  tailscale/tsrecorder:stable \\\r\n  /tsrecorder --dst=/data/recordings --statedir=/data/state --ui\r\n```\r\n\r\nRequired flags:\r\n\r\n- `--dst` Specifies where recordings will be saved. Accepts a local file path or an S3 region URL. You can alternatively define the value as an environment variable, `TSRECORDER_DST`.\r\n\r\nOptional flags:\r\n\r\n- `--hostname` Specifies a hostname to use for the recorder node. Defaults to `recorder`. You can alternatively define the value as an environment variable, `TSRECORDER_HOSTNAME`.\r\n- `--access-key` The AWS access key for an IAM user that can upload recordings to an S3 bucket. Required when using S3 as a storage backend if no IAM role is passed to the instance. You can alternatively define the secret access key as an environment variable, `AWS_ACCESS_KEY_ID`.\r\n- `--secret-key` The AWS secret access key for an IAM user that can upload recordings to an S3 bucket. Required when using S3 as a storage backend if no IAM role is passed to the instance. You can alternatively define the secret access key as an environment variable, `AWS_SECRET_ACCESS_KEY`.\r\n- `--bucket` The name of the S3 bucket where the recorder should upload recordings. Required when using S3 as a storage backend. You can alternatively define the secret access key as an environment variable, `TSRECORDER_BUCKET`.\r\n- `--state` Path to Tailscale state file, used to persist recorder state across container restarts. This can be a persistent volume or a Kubernetes [`Secret`](https://kubernetes.io/docs/concepts/configuration/secret/). You can alternatively define this value as an environment variable, `TS_STATE`.\r\n  - To use a `Secret`, specify `kube:<secret-name>`. If set to use a `Secret` for state storage, the recorder must have permissions to read, update, and patch the `Secret`. If the `Secret` does not exist, the recorder will create it, and must have permissions to do so.\r\n  - If `--state` is unset, the state will be stored in the directory set by using `--statedir`.\r\n- `--statedir` Specifies where the recorder should store its internal state. Accepts a local file path. If storage backend is filesystem, defaults to `<recordings-destination>/state`. Required unless statefile path is set. Alternatively can be set by using `TS_STATE_DIR` environment variable.\r\n- `--ui` Enables the recorder container web UI for viewing recorded SSH sessions. Defaults to `false` if this flag is not present. You can alternatively define this value as an environment variable, `TSRECORDER_UI`.\r\n  - If you deploy the recorder with the UI, you must have HTTPS enabled in your tailnet.\r\n  - If you enable the recorder container web UI, you should restrict access to port `443` on the recorder in your access control policies to prevent unauthorized members of your tailnet from viewing sensitive recordings.\r\n\r\n### [Turn on session recording in your tailnet policy file](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#turn-on-session-recording-in-your-tailnet-policy-file)\r\n\r\nYou can turn on session recording on a per-access rule basis for SSH [access rules](https://tailscale.com/docs/reference/syntax/policy-file#acls) in your tailnet policy file. For each rule, add a `recorder` field and specify the tag (`[\"tag:<tag-name>\"]`) that is attached to the recorder node. If no `recorder` is specified, the session is not recorded.\r\n\r\nBy default, Tailscale will allow a Tailscale SSH session to connect when session recording is enabled for its SSH access rule even if the recorder nodes are unreachable. We refer to this as \"failing open.\" You can adjust this behavior by adding the `enforceRecorder` field and setting it to `true`. This will deny and/or stop any Tailscale SSH session that should be recorded when the recorder nodes are unreachable. We refer to this behavior as \"failing closed\".\r\n\r\nIf an SSH connection is allowed by two or more SSH access rules, and the access rules specify different values for `enforceRecorder`, Tailscale will follow the behavior that is listed first in the tailnet policy file.\r\n\r\n```json\r\n{\r\n  \"action\": \"check\", // \"accept\" or \"check\"\r\n  \"src\": [list-of-sources],\r\n  \"dst\": [list-of-destinations],\r\n  \"users\": [list-of-ssh-users],\r\n  \"recorder\": [\"tag:<tag-name>\"], // optional field; specify the tag attached to your recorder node.\r\n  \"enforceRecorder\": false, // optional field; defaults to false; if session recorder node is unavailable, should the session be denied?\r\n},\r\n```\r\n\r\nIf `enforceRecorder` is `true` and the recorder node becomes unavailable after session recording is enabled in access control policies, Tailscale SSH sessions will be refused. Any active Tailscale SSH sessions will be terminated. To establish a Tailscale SSH session when the recorder node is unreachable and set to fail closed, remove the `recorder` field in access control policies.\r\n\r\n## [Scope access to the recorder node and host](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#scope-access-to-the-recorder-node-and-host)\r\n\r\nEach Tailscale SSH server sends its SSH session recordings to the recorder node on port `80`. Tailscale will automatically ensure that traffic is permitted between Tailscale SSH servers and the recorder nodes to which they send sessions. You don't need to make any changes in access control policies to allow this.\r\n\r\nIf you enabled the recorder container web UI, you should be sure to allow access to port `443` for the appropriate users. Like before, use the associated tag to write this access rule.\r\n\r\nBecause the recorder stores the session recordings on the host disk, you should ensure that you have restricted access to these environments to the right users. We recommend scoping access to the recorder node and its host to the fewest number of users necessary, and recall that Owners, Admins, and Network admins will all have access to change access rules for this feature.\r\n\r\n## [Disable session recording](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#disable-session-recording)\r\n\r\nYou can disable Tailscale SSH session recording for specific SSH access rules, or for the whole tailnet.\r\n\r\n### [Disable session recording for specific Tailscale SSH access rules](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#disable-session-recording-for-specific-tailscale-ssh-access-rules)\r\n\r\nTo disable Tailscale SSH session recording for specific SSH access rules, remove the `recorder` line from the SSH access rule. SSH sessions that meet that rule will no longer be recorded.\r\n\r\nPrevious recordings will not be removed, and need to be deleted from the recorder node.\r\n\r\n### [Disable session recording for all Tailscale SSH sessions](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#disable-session-recording-for-all-tailscale-ssh-sessions)\r\n\r\nTo disable Tailscale SSH session recording for all Tailscale SSH sessions in the tailnet:\r\n\r\n- Remove the `recorder` rule from all SSH access rules in the `ssh` section of your tailnet policy file\r\n- Remove the recorder node from your tailnet. This can be done by removing the device from the [Machines](https://login.tailscale.com/admin/machines) tab of the admin console.\r\n\r\n## [Session recordings](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#session-recordings)\r\n\r\nRecordings are stored at the directory you specified, as `/dir/<stablenodeid>/<timestamp>.cast`. The `<stablenodeid>` directory corresponds to the [stable node ID](https://tailscale.com/api#tag/devices) of the SSH servers being accessed.\r\n\r\nIf you run out of disk on the host, the recorder can no longer store sessions and will fail. This will cause open sessions to be terminated and new sessions to be refused. To fix this, remove the `recorder` field from the SSH access rule for your machine, access the node host, clear space or add storage, and replace the `recorder` field.\r\n\r\n### [Reviewing recordings](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#reviewing-recordings)\r\n\r\nTailscale SSH session recording captures each SSH session in `asciinema` format. This format can be played to review what occurred in a session.\r\n\r\nIf you are deploying the recorder with the built-in web UI turned on, you can review your recordings from a web browser at the URL. You can find your [tailnet DNS name](https://tailscale.com/docs/concepts/tailnet-name#tailnet-dns-name) in the [DNS](https://login.tailscale.com/admin/dns) page of the admin console.\r\n\r\n`https://{recorder-name}.{tailnet-dns-name}.ts.net`\r\n\r\nFrom the CLI, you can access the recording data using the command:\r\n\r\n```shell\r\ncat <session-recording.cast>\r\n```\r\n\r\nYou can also watch recordings from the CLI with `asciinema` using the command:\r\n\r\n```shell\r\nplay <session-recording.cast>\r\n```\r\n\r\nTo install `asciinema` locally, refer to the [installation docs](https://docs.asciinema.org/manual/cli/installation) for your platform.\r\n\r\nYou can also convert the recording to a GIF using [`agg`](https://github.com/asciinema/agg).\r\n\r\n### [Analyzing recordings](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#analyzing-recordings)\r\n\r\nRecordings use the `asciinema` format, so they are newline-delimited JSON files that can be searched as text. For example, to search a recording for `sudo`:\r\n\r\n```shell\r\ngrep \"sudo\" <session-recording.cast>\r\n```\r\n\r\n### [Example recording](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#example-recording)\r\n\r\nHere's an example of a short session recording being replayed using `asciinema`:\r\n\r\n![A recording of a terminal, made with asciinema, where the user types 'echo hi' and the terminal prints 'hi'.](https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fssh-recording-demo.904d935d.gif&w=3840&q=75)\r\n\r\nA demo of the asciinema session recording playback.\r\n\r\nThis is how the same recording is represented in the `asciinema` file. It's easy to search this file for keywords or patterns that interest you.\r\n\r\n```markup\r\n{\"version\":2,\"width\":203,\"height\":38,\"timestamp\":1679441819,\"env\":{\"TERM\":\"xterm-256color\"},\"srcNode\":\"srcnode.ts.net\",\"srcNodeID\":\"nguedK2CNTRL\",\"srcNodeTags\":null,\"sshUser\":\"alice\",\"localUser\":\"alice\",\"srcNodeUserID\":30585448562688899,\"srcNodeUser\":\"alice@tailscale.com\"}\r\n[0.456997416,\"o\",\"Last login: Tue Mar 21 16:35:14 from 1.2.3.4\\r\\n\"]\r\n[0.552500666,\"o\",\"\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                                                                                                                                                          \\r \\r\"]\\\r\n[0.557596708,\"o\",\"\\u001b]2;alice@laptop:~\\u0007\\u001b]1;~\\u0007\"]\\\r\n[0.567016125,\"o\",\"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J\\u001b[01;32m➜  \\u001b[36m~\\u001b[00m \\u001b[K\"]\\\r\n[0.567112833,\"o\",\"\\u001b[?1h\\u001b=\\u001b[?2004h\"]\\\r\n[1.500827583,\"o\",\"e\"]\\\r\n[1.58455025,\"o\",\"\\u0008ec\"]\\\r\n[1.6682777500000001,\"o\",\"h\"]\\\r\n[1.7546742499999999,\"o\",\"o\"]\\\r\n[1.896455708,\"o\",\" \"]\\\r\n[2.020248958,\"o\",\"h\"]\\\r\n[2.08789675,\"o\",\"i\"]\\\r\n[2.323278875,\"o\",\"\\u001b[?1l\\u001b\\u003e\"]\\\r\n[2.323438208,\"o\",\"\\u001b[?2004l\\r\\r\\n\"]\\\r\n[2.324209,\"o\",\"\\u001b]2;echo hi\\u0007\"]\\\r\n[2.324296291,\"o\",\"\\u001b]1;echo\\u0007hi\\r\\n\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                                                                                                                                                          \\r \\r\"]\\\r\n[2.334692083,\"o\",\"\\u001b]2;alice@laptop:~\\u0007\\u001b]1;~\\u0007\"]\\\r\n[2.349814583,\"o\",\"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J\\u001b[01;32m➜  \\u001b[36m~\\u001b[00m \\u001b[K\"]\\\r\n[2.349910875,\"o\",\"\\u001b[?1h\\u001b=\\u001b[?2004h\"]\\\r\n[2.89156075,\"o\",\"\\u001b[?2004l\\r\\r\\n\"]\\\r\n```\\\r\n\\\r\n### [(Optional) Deploy multiple recorders for failover](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#optional-deploy-multiple-recorders-for-failover)\\\r\n\\\r\nIf you are running Tailscale SSH session recording in a production environment, you may want to [deploy multiple recorders in a failover configuration](https://tailscale.com/docs/reference/multiple-recorder-nodes) to prevent unwanted downtime.\\\r\n\\\r\n## [Limitations](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#limitations)\\\r\n\\\r\n- Recordings are only available for Tailscale SSH sessions, not other SSH sessions over Tailscale.\\\r\n- Recordings are streamed from the destination node of the Tailscale SSH connection, and cannot be configured to be streamed from the source node.\\\r\n- Session recording only records outputs from an SSH session, not inputs.\\\r\n- All outputs are logged, including any sensitive data that might be in the output.\\\r\n\\\r\n![Project Logo](https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed)\\\r\n\\\r\nAsk AI\\\r\n\\\r\nreCAPTCHA\\\r\n\\\r\nRecaptcha requires verification.\\\r\n\\\r\nprotected by **reCAPTCHA**\r\n","html":"<h1>Tailscale SSH session recording</h1>\n<p>Last validated: Jan 5, 2026</p>\n<p>Tailscale SSH session recording lets you stream logs of <a href=\"https://tailscale.com/docs/features/tailscale-ssh\">Tailscale SSH</a> sessions to another node in your tailnet. These recordings are encrypted end-to-end just like all other Tailscale traffic.</p>\n<p>Tailscale SSH session recordingis currently <a href=\"https://tailscale.com/docs/reference/tailscale-release-stages#beta\">in beta</a>. To try it, follow the steps below to enable it for your network using Tailscale v1.40.1 or later.</p>\n<p>Tailscale SSH session recordingis available for <a href=\"https://tailscale.com/pricing\">the Personal and Enterprise plans</a>.</p>\n<p>The Tailscale session recorder now supports recording all <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/session-recording\">Kubernetes sessions and API requests</a>.</p>\n<h2><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#how-it-works\">How it works</a></h2>\n<p>Tailscale SSH session recording lets you stream recordings of Tailscale SSH sessions from the destination node to a recorder node in your tailnet. The recorder is a <a href=\"https://tailscale.com/blog/tsnet-virtual-private-services\"><code>tsnet</code> binary</a>, which you deploy to a machine or <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder\">Kubernetes cluster</a> you manage. Logs are streamed over your tailnet to your node, so they are end-to-end encrypted and not visible to Tailscale. The recorder can either store the log files on the host filesystem, or export them to Amazon S3 or another S3-compatible storage service.</p>\n<p>Tailscale SSH session recording captures terminal sessions in <a href=\"https://asciinema.org/\"><code>asciinema</code></a> format. These recordings are newline-delimited JSON files that can be searched as text, and replayed from the recorder node's optional web UI or the <code>asciinema</code> CLI.</p>\n<p>Session recording captures all terminal output, meaning any text that appears on the terminal screen during a Tailscale SSH session. This could include sensitive data. No keystrokes are captured.</p>\n<p>When using binary protocols like older versions of <code>scp</code>, session recording will capture the transmitted binary data. This could include sensitive data.</p>\n<h2><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#set-up-session-recording-for-tailscale-ssh\">Set up session recording for Tailscale SSH</a></h2>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#prerequisites\">Prerequisites</a></h3>\n<p>Session recording is only available for SSH connections over Tailscale SSH.</p>\n<p>You need to be an <a href=\"https://tailscale.com/docs/reference/user-roles\">Owner, Admin, or Network admin</a>\r\nof a tailnet to set up session recording.</p>\n<p>To enable session recording, you must:</p>\n<ul>\n<li>Ensure that Tailscale SSH is properly configured in your tailnet.</li>\n<li>Deploy a recorder node in your tailnet.</li>\n<li>Turn on session recording in your tailnet policy file by adding a <code>recorder:[\"tag:&#x3C;tag-name>\"]</code> field to each SSH policy entry. SSH sessions permitted by that stanza will be streamed to any recorder with the named tag.</li>\n</ul>\n<p>The recorder node binary will run on any platform as a Docker container. Because the binary uses <a href=\"https://tailscale.com/blog/tsnet-virtual-private-services\"><code>tsnet</code></a> to join your tailnet as a node, the Docker host does not need to be a member of your tailnet. The application listens on port <code>80</code> to receive session recordings, and provides an optional, built-in web application for viewing session recordings at port <code>443</code>.</p>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#create-a-tag-and-auth-key-for-session-recorders\">Create a tag and auth key for session recorders</a></h3>\n<p>A recorder node must be associated with a tag. We recommend that you create a new <a href=\"https://tailscale.com/docs/features/tags\">tag</a> specifically to use with SSH session recording. You can do this by visiting the <a href=\"https://login.tailscale.com/admin/acls\">Access controls</a> page of the admin console and defining an owner of the tag in access control policies.</p>\n<pre><code class=\"language-json\">// Example tag for SSH session recorders\r\n\"tagOwners\": {\r\n  \"tag:session-recorder\": [\\\r\n    \"&#x3C;tag-owner>\",\\\r\n  ],\r\n}\n</code></pre>\n<p>Next, visit the <a href=\"https://login.tailscale.com/admin/settings/keys\">Keys</a> page of the admin console to create an auth key. Assign the new tag you created to this auth key. If your tailnet has device approval enabled, use a pre-approved key. Be sure to copy this auth key to your clipboard.</p>\n<p>On the host where you will deploy the recorder node container, set the <code>TS_AUTHKEY</code> environment variable to the auth key. This is only necessary the first time you start the recorder application.</p>\n<pre><code class=\"language-markup\">export TS_AUTHKEY=&#x3C;your-auth-key>\n</code></pre>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#deploy-a-recorder-node\">Deploy a recorder node</a></h3>\n<p>The recorder is provided as a Docker container, and you can find it on <a href=\"https://hub.docker.com/r/tailscale/tsrecorder/tags\">Tailscale's DockerHub profile</a>.</p>\n<p>If you use Tailscale's Kubernetes operator, you can also deploy the recorder using the <a href=\"https://tailscale.com/docs/features/kubernetes-operator/how-to/tsrecorder\"><code>Recorder</code> Custom Resource</a>.</p>\n<pre><code class=\"language-shell\">docker pull tailscale/tsrecorder:stable\n</code></pre>\n<p>To run the container and save recordings to the Docker host's filesystem:</p>\n<pre><code class=\"language-shell\">docker run --name tsrecorder --rm -it \\\r\n  -e TS_AUTHKEY=$TS_AUTHKEY \\\r\n  -v $HOME/tsrecorder:/data \\\r\n  tailscale/tsrecorder:stable \\\r\n  /tsrecorder --dst=/data/recordings --statedir=/data/state --ui\n</code></pre>\n<p>Required flags:</p>\n<ul>\n<li><code>--dst</code> Specifies where recordings will be saved. Accepts a local file path or an S3 region URL. You can alternatively define the value as an environment variable, <code>TSRECORDER_DST</code>.</li>\n</ul>\n<p>Optional flags:</p>\n<ul>\n<li><code>--hostname</code> Specifies a hostname to use for the recorder node. Defaults to <code>recorder</code>. You can alternatively define the value as an environment variable, <code>TSRECORDER_HOSTNAME</code>.</li>\n<li><code>--access-key</code> The AWS access key for an IAM user that can upload recordings to an S3 bucket. Required when using S3 as a storage backend if no IAM role is passed to the instance. You can alternatively define the secret access key as an environment variable, <code>AWS_ACCESS_KEY_ID</code>.</li>\n<li><code>--secret-key</code> The AWS secret access key for an IAM user that can upload recordings to an S3 bucket. Required when using S3 as a storage backend if no IAM role is passed to the instance. You can alternatively define the secret access key as an environment variable, <code>AWS_SECRET_ACCESS_KEY</code>.</li>\n<li><code>--bucket</code> The name of the S3 bucket where the recorder should upload recordings. Required when using S3 as a storage backend. You can alternatively define the secret access key as an environment variable, <code>TSRECORDER_BUCKET</code>.</li>\n<li><code>--state</code> Path to Tailscale state file, used to persist recorder state across container restarts. This can be a persistent volume or a Kubernetes <a href=\"https://kubernetes.io/docs/concepts/configuration/secret/\"><code>Secret</code></a>. You can alternatively define this value as an environment variable, <code>TS_STATE</code>.\n<ul>\n<li>To use a <code>Secret</code>, specify <code>kube:&#x3C;secret-name></code>. If set to use a <code>Secret</code> for state storage, the recorder must have permissions to read, update, and patch the <code>Secret</code>. If the <code>Secret</code> does not exist, the recorder will create it, and must have permissions to do so.</li>\n<li>If <code>--state</code> is unset, the state will be stored in the directory set by using <code>--statedir</code>.</li>\n</ul>\n</li>\n<li><code>--statedir</code> Specifies where the recorder should store its internal state. Accepts a local file path. If storage backend is filesystem, defaults to <code>&#x3C;recordings-destination>/state</code>. Required unless statefile path is set. Alternatively can be set by using <code>TS_STATE_DIR</code> environment variable.</li>\n<li><code>--ui</code> Enables the recorder container web UI for viewing recorded SSH sessions. Defaults to <code>false</code> if this flag is not present. You can alternatively define this value as an environment variable, <code>TSRECORDER_UI</code>.\n<ul>\n<li>If you deploy the recorder with the UI, you must have HTTPS enabled in your tailnet.</li>\n<li>If you enable the recorder container web UI, you should restrict access to port <code>443</code> on the recorder in your access control policies to prevent unauthorized members of your tailnet from viewing sensitive recordings.</li>\n</ul>\n</li>\n</ul>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#turn-on-session-recording-in-your-tailnet-policy-file\">Turn on session recording in your tailnet policy file</a></h3>\n<p>You can turn on session recording on a per-access rule basis for SSH <a href=\"https://tailscale.com/docs/reference/syntax/policy-file#acls\">access rules</a> in your tailnet policy file. For each rule, add a <code>recorder</code> field and specify the tag (<code>[\"tag:&#x3C;tag-name>\"]</code>) that is attached to the recorder node. If no <code>recorder</code> is specified, the session is not recorded.</p>\n<p>By default, Tailscale will allow a Tailscale SSH session to connect when session recording is enabled for its SSH access rule even if the recorder nodes are unreachable. We refer to this as \"failing open.\" You can adjust this behavior by adding the <code>enforceRecorder</code> field and setting it to <code>true</code>. This will deny and/or stop any Tailscale SSH session that should be recorded when the recorder nodes are unreachable. We refer to this behavior as \"failing closed\".</p>\n<p>If an SSH connection is allowed by two or more SSH access rules, and the access rules specify different values for <code>enforceRecorder</code>, Tailscale will follow the behavior that is listed first in the tailnet policy file.</p>\n<pre><code class=\"language-json\">{\r\n  \"action\": \"check\", // \"accept\" or \"check\"\r\n  \"src\": [list-of-sources],\r\n  \"dst\": [list-of-destinations],\r\n  \"users\": [list-of-ssh-users],\r\n  \"recorder\": [\"tag:&#x3C;tag-name>\"], // optional field; specify the tag attached to your recorder node.\r\n  \"enforceRecorder\": false, // optional field; defaults to false; if session recorder node is unavailable, should the session be denied?\r\n},\n</code></pre>\n<p>If <code>enforceRecorder</code> is <code>true</code> and the recorder node becomes unavailable after session recording is enabled in access control policies, Tailscale SSH sessions will be refused. Any active Tailscale SSH sessions will be terminated. To establish a Tailscale SSH session when the recorder node is unreachable and set to fail closed, remove the <code>recorder</code> field in access control policies.</p>\n<h2><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#scope-access-to-the-recorder-node-and-host\">Scope access to the recorder node and host</a></h2>\n<p>Each Tailscale SSH server sends its SSH session recordings to the recorder node on port <code>80</code>. Tailscale will automatically ensure that traffic is permitted between Tailscale SSH servers and the recorder nodes to which they send sessions. You don't need to make any changes in access control policies to allow this.</p>\n<p>If you enabled the recorder container web UI, you should be sure to allow access to port <code>443</code> for the appropriate users. Like before, use the associated tag to write this access rule.</p>\n<p>Because the recorder stores the session recordings on the host disk, you should ensure that you have restricted access to these environments to the right users. We recommend scoping access to the recorder node and its host to the fewest number of users necessary, and recall that Owners, Admins, and Network admins will all have access to change access rules for this feature.</p>\n<h2><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#disable-session-recording\">Disable session recording</a></h2>\n<p>You can disable Tailscale SSH session recording for specific SSH access rules, or for the whole tailnet.</p>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#disable-session-recording-for-specific-tailscale-ssh-access-rules\">Disable session recording for specific Tailscale SSH access rules</a></h3>\n<p>To disable Tailscale SSH session recording for specific SSH access rules, remove the <code>recorder</code> line from the SSH access rule. SSH sessions that meet that rule will no longer be recorded.</p>\n<p>Previous recordings will not be removed, and need to be deleted from the recorder node.</p>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#disable-session-recording-for-all-tailscale-ssh-sessions\">Disable session recording for all Tailscale SSH sessions</a></h3>\n<p>To disable Tailscale SSH session recording for all Tailscale SSH sessions in the tailnet:</p>\n<ul>\n<li>Remove the <code>recorder</code> rule from all SSH access rules in the <code>ssh</code> section of your tailnet policy file</li>\n<li>Remove the recorder node from your tailnet. This can be done by removing the device from the <a href=\"https://login.tailscale.com/admin/machines\">Machines</a> tab of the admin console.</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#session-recordings\">Session recordings</a></h2>\n<p>Recordings are stored at the directory you specified, as <code>/dir/&#x3C;stablenodeid>/&#x3C;timestamp>.cast</code>. The <code>&#x3C;stablenodeid></code> directory corresponds to the <a href=\"https://tailscale.com/api#tag/devices\">stable node ID</a> of the SSH servers being accessed.</p>\n<p>If you run out of disk on the host, the recorder can no longer store sessions and will fail. This will cause open sessions to be terminated and new sessions to be refused. To fix this, remove the <code>recorder</code> field from the SSH access rule for your machine, access the node host, clear space or add storage, and replace the <code>recorder</code> field.</p>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#reviewing-recordings\">Reviewing recordings</a></h3>\n<p>Tailscale SSH session recording captures each SSH session in <code>asciinema</code> format. This format can be played to review what occurred in a session.</p>\n<p>If you are deploying the recorder with the built-in web UI turned on, you can review your recordings from a web browser at the URL. You can find your <a href=\"https://tailscale.com/docs/concepts/tailnet-name#tailnet-dns-name\">tailnet DNS name</a> in the <a href=\"https://login.tailscale.com/admin/dns\">DNS</a> page of the admin console.</p>\n<p><code>https://{recorder-name}.{tailnet-dns-name}.ts.net</code></p>\n<p>From the CLI, you can access the recording data using the command:</p>\n<pre><code class=\"language-shell\">cat &#x3C;session-recording.cast>\n</code></pre>\n<p>You can also watch recordings from the CLI with <code>asciinema</code> using the command:</p>\n<pre><code class=\"language-shell\">play &#x3C;session-recording.cast>\n</code></pre>\n<p>To install <code>asciinema</code> locally, refer to the <a href=\"https://docs.asciinema.org/manual/cli/installation\">installation docs</a> for your platform.</p>\n<p>You can also convert the recording to a GIF using <a href=\"https://github.com/asciinema/agg\"><code>agg</code></a>.</p>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#analyzing-recordings\">Analyzing recordings</a></h3>\n<p>Recordings use the <code>asciinema</code> format, so they are newline-delimited JSON files that can be searched as text. For example, to search a recording for <code>sudo</code>:</p>\n<pre><code class=\"language-shell\">grep \"sudo\" &#x3C;session-recording.cast>\n</code></pre>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording#example-recording\">Example recording</a></h3>\n<p>Here's an example of a short session recording being replayed using <code>asciinema</code>:</p>\n<p><img src=\"https://tailscale.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fssh-recording-demo.904d935d.gif&#x26;w=3840&#x26;q=75\" alt=\"A recording of a terminal, made with asciinema, where the user types &#x27;echo hi&#x27; and the terminal prints &#x27;hi&#x27;.\"></p>\n<p>A demo of the asciinema session recording playback.</p>\n<p>This is how the same recording is represented in the <code>asciinema</code> file. It's easy to search this file for keywords or patterns that interest you.</p>\n<pre><code class=\"language-markup\">{\"version\":2,\"width\":203,\"height\":38,\"timestamp\":1679441819,\"env\":{\"TERM\":\"xterm-256color\"},\"srcNode\":\"srcnode.ts.net\",\"srcNodeID\":\"nguedK2CNTRL\",\"srcNodeTags\":null,\"sshUser\":\"alice\",\"localUser\":\"alice\",\"srcNodeUserID\":30585448562688899,\"srcNodeUser\":\"alice@tailscale.com\"}\r\n[0.456997416,\"o\",\"Last login: Tue Mar 21 16:35:14 from 1.2.3.4\\r\\n\"]\r\n[0.552500666,\"o\",\"\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                                                                                                                                                          \\r \\r\"]\\\r\n[0.557596708,\"o\",\"\\u001b]2;alice@laptop:~\\u0007\\u001b]1;~\\u0007\"]\\\r\n[0.567016125,\"o\",\"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J\\u001b[01;32m➜  \\u001b[36m~\\u001b[00m \\u001b[K\"]\\\r\n[0.567112833,\"o\",\"\\u001b[?1h\\u001b=\\u001b[?2004h\"]\\\r\n[1.500827583,\"o\",\"e\"]\\\r\n[1.58455025,\"o\",\"\\u0008ec\"]\\\r\n[1.6682777500000001,\"o\",\"h\"]\\\r\n[1.7546742499999999,\"o\",\"o\"]\\\r\n[1.896455708,\"o\",\" \"]\\\r\n[2.020248958,\"o\",\"h\"]\\\r\n[2.08789675,\"o\",\"i\"]\\\r\n[2.323278875,\"o\",\"\\u001b[?1l\\u001b\\u003e\"]\\\r\n[2.323438208,\"o\",\"\\u001b[?2004l\\r\\r\\n\"]\\\r\n[2.324209,\"o\",\"\\u001b]2;echo hi\\u0007\"]\\\r\n[2.324296291,\"o\",\"\\u001b]1;echo\\u0007hi\\r\\n\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                                                                                                                                                          \\r \\r\"]\\\r\n[2.334692083,\"o\",\"\\u001b]2;alice@laptop:~\\u0007\\u001b]1;~\\u0007\"]\\\r\n[2.349814583,\"o\",\"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J\\u001b[01;32m➜  \\u001b[36m~\\u001b[00m \\u001b[K\"]\\\r\n[2.349910875,\"o\",\"\\u001b[?1h\\u001b=\\u001b[?2004h\"]\\\r\n[2.89156075,\"o\",\"\\u001b[?2004l\\r\\r\\n\"]\\\r\n```\\\r\n\\\r\n### [(Optional) Deploy multiple recorders for failover](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#optional-deploy-multiple-recorders-for-failover)\\\r\n\\\r\nIf you are running Tailscale SSH session recording in a production environment, you may want to [deploy multiple recorders in a failover configuration](https://tailscale.com/docs/reference/multiple-recorder-nodes) to prevent unwanted downtime.\\\r\n\\\r\n## [Limitations](https://tailscale.com/docs/features/tailscale-ssh/tailscale-ssh-session-recording\\#limitations)\\\r\n\\\r\n- Recordings are only available for Tailscale SSH sessions, not other SSH sessions over Tailscale.\\\r\n- Recordings are streamed from the destination node of the Tailscale SSH connection, and cannot be configured to be streamed from the source node.\\\r\n- Session recording only records outputs from an SSH session, not inputs.\\\r\n- All outputs are logged, including any sensitive data that might be in the output.\\\r\n\\\r\n![Project Logo](https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed)\\\r\n\\\r\nAsk AI\\\r\n\\\r\nreCAPTCHA\\\r\n\\\r\nRecaptcha requires verification.\\\r\n\\\r\nprotected by **reCAPTCHA**\n</code></pre>\n"}