{"slug":"send-tailscale-ssh-session-recordings-to-s3","title":"Send Tailscale SSH session recordings to S3","tags":["tailscale"],"agent_summary":"Last validated: Jan 9, 2026","trigger_phrases":[],"runnable":false,"markdown":"\r\n# Send Tailscale SSH session recordings to S3\r\n\r\nLast validated: Jan 9, 2026\r\n\r\nBy default, Tailscale SSH session recording will save recordings to the Docker host's filesystem. While this is a fast and straightforward way to deploy session recorders, you may want to use a more scalable and resilient storage solution like Amazon S3.\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\nYou can configure session recorder nodes to send recordings to Amazon S3 or another S3-compatible object storage service such as MinIO, Wasabi, Google Cloud Storage, or [Cloudflare R2](https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3#cloudflare-r2-compatibility).\r\n\r\nIf you deploy recorder nodes with the built-in web UI enabled, users can access recordings stored in S3 in the web UI.\r\n\r\n## [Configure S3 as storage for session recording nodes](https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3\\#configure-s3-as-storage-for-session-recording-nodes)\r\n\r\n### [Prerequisites](https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3\\#prerequisites)\r\n\r\nTo configure S3 as the backend storage for session recording, you must have access to an AWS account with permission to create:\r\n\r\n- S3 buckets\r\n- IAM policies\r\n\r\nand _either_\r\n\r\n- IAM users\r\n- IAM user access keys\r\n\r\nor\r\n\r\n- an IAM role\r\n\r\n### [Create an IAM policy and user/role](https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3\\#create-an-iam-policy-and-userrole)\r\n\r\n[Create the following IAM policy](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create-console.html) in your AWS account. Replace `<bucket-name>` with the name of your S3 bucket on both lines of the `Resource` section.\r\n\r\n```json\r\n{\r\n  \"Version\": \"2012-10-17\",\r\n  \"Statement\": [\\\r\n    {\\\r\n      \"Effect\": \"Allow\",\\\r\n      \"Action\": [\\\r\n        \"s3:PutObject\",\\\r\n        \"s3:GetBucketLocation\",\\\r\n        \"s3:GetObject\",\\\r\n        \"s3:ListBucket\"\\\r\n      ],\\\r\n      \"Resource\": [\\\r\n        \"arn:aws:s3:::<bucket-name>/*\",\\\r\n        \"arn:aws:s3:::<bucket-name>\"\\\r\n      ]\\\r\n    }\\\r\n  ]\r\n}\r\n```\r\n\r\nIf you are running the recorder nodes without the built-in web UI, you can omit `s3:GetObject` and `s3:ListBucket` from this policy. `s3:PutObject` and `s3:GetBucketLocation` are always required.\r\n\r\nIf you plan to use IAM access keys for permission to write to the bucket, assign this policy a new IAM user, and [create an access key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey) for this user. If you plan to use an IAM role, [create an IAM role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create.html) and attach the previously defined policy to the role.\r\n\r\n### [Deploy the recorder node](https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3\\#deploy-the-recorder-node)\r\n\r\nDeploying a recorder node with S3 as the storage backend is similar to the standard filesystem deployment. You'll need to add your IAM credentials or role, and the S3 bucket information.\r\n\r\n#### [Deploy with AWS access keys](https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3\\#deploy-with-aws-access-keys)\r\n\r\nFor AWS access keys, specify the AWS access key and secret key:\r\n\r\n```shell\r\ndocker run --name tsrecorder --rm -it \\\r\n  -e TS_AUTHKEY=$TS_AUTHKEY \\\r\n  -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY \\\r\n  -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \\\r\n  -v $HOME/tsrecorder:/data \\\r\n  tailscale/tsrecorder:stable \\\r\n  /tsrecorder --dst='s3://s3.us-east-2.amazonaws.com' --statedir=/data/state \\\r\n  --bucket=$S3_BUCKET_NAME --ui \\\r\n```\r\n\r\n#### [Deploy with attached IAM roles](https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3\\#deploy-with-attached-iam-roles)\r\n\r\nFor IAM roles, the credentials are retrieved from the AWS metadata service. You'll need to assign an IAM role (and if using an EC2 instance, an [IAM instance profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html)) to the container.\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='s3://s3.us-east-2.amazonaws.com' --statedir=/data/state \\\r\n  --bucket=$S3_BUCKET_NAME --ui \\\r\n```\r\n\r\nIf the instance you're provisioning the Docker container is using [Instance Metadata v2](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/configuring-instance-metadata-service.html), the default `PUT` response hop limit is 1 (that is, from the EC2 instance to the metadata service). For IAM instance profiles to work with Docker, you must first configure the limit to account for the extra hop through the EC2 instance, [so 2 at minimum](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/configuring-IMDS-existing-instances.html#modify-PUT-response-hop-limit).\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. The value must begin with `s3://` if you intend to use S3 storage for storing the recordings. You can alternatively define the value as an environment variable, `TSRECORDER_DST`.\r\n- `--statedir` Specifies where the recorder should store its internal state. Accepts a local file path.\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 using `--statedir`.\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\nThere are multiple ways to define the AWS IAM credentials needed to connect to an S3 bucket. In order of precedence, they are:\r\n\r\n1. Specify `--access-key` and `--secret-key`\r\n2. Set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`\r\n3. Specify none of the above, and be on an EC2 instance with the right permissions.\r\n\r\n#### [Cloudflare R2 Compatibility](https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3\\#cloudflare-r2-compatibility)\r\n\r\nCloudflare R2 Storage provides an [S3-compatible API](https://developers.cloudflare.com/r2/api/s3/api); however, an additional setting is needed due to differences in their implementation. To use Cloudflare R2 as the storage service for session recordings set the environment variable `S3_SEND_CONTENT_MD5` to `true` in addition to any environment variables you are already passing.\r\n\r\n```shell\r\nS3_SEND_CONTENT_MD5=true\r\n```\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>Send Tailscale SSH session recordings to S3</h1>\n<p>Last validated: Jan 9, 2026</p>\n<p>By default, Tailscale SSH session recording will save recordings to the Docker host's filesystem. While this is a fast and straightforward way to deploy session recorders, you may want to use a more scalable and resilient storage solution like Amazon S3.</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>You can configure session recorder nodes to send recordings to Amazon S3 or another S3-compatible object storage service such as MinIO, Wasabi, Google Cloud Storage, or <a href=\"https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3#cloudflare-r2-compatibility\">Cloudflare R2</a>.</p>\n<p>If you deploy recorder nodes with the built-in web UI enabled, users can access recordings stored in S3 in the web UI.</p>\n<h2><a href=\"https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3#configure-s3-as-storage-for-session-recording-nodes\">Configure S3 as storage for session recording nodes</a></h2>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3#prerequisites\">Prerequisites</a></h3>\n<p>To configure S3 as the backend storage for session recording, you must have access to an AWS account with permission to create:</p>\n<ul>\n<li>S3 buckets</li>\n<li>IAM policies</li>\n</ul>\n<p>and <em>either</em></p>\n<ul>\n<li>IAM users</li>\n<li>IAM user access keys</li>\n</ul>\n<p>or</p>\n<ul>\n<li>an IAM role</li>\n</ul>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3#create-an-iam-policy-and-userrole\">Create an IAM policy and user/role</a></h3>\n<p><a href=\"https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create-console.html\">Create the following IAM policy</a> in your AWS account. Replace <code>&#x3C;bucket-name></code> with the name of your S3 bucket on both lines of the <code>Resource</code> section.</p>\n<pre><code class=\"language-json\">{\r\n  \"Version\": \"2012-10-17\",\r\n  \"Statement\": [\\\r\n    {\\\r\n      \"Effect\": \"Allow\",\\\r\n      \"Action\": [\\\r\n        \"s3:PutObject\",\\\r\n        \"s3:GetBucketLocation\",\\\r\n        \"s3:GetObject\",\\\r\n        \"s3:ListBucket\"\\\r\n      ],\\\r\n      \"Resource\": [\\\r\n        \"arn:aws:s3:::&#x3C;bucket-name>/*\",\\\r\n        \"arn:aws:s3:::&#x3C;bucket-name>\"\\\r\n      ]\\\r\n    }\\\r\n  ]\r\n}\n</code></pre>\n<p>If you are running the recorder nodes without the built-in web UI, you can omit <code>s3:GetObject</code> and <code>s3:ListBucket</code> from this policy. <code>s3:PutObject</code> and <code>s3:GetBucketLocation</code> are always required.</p>\n<p>If you plan to use IAM access keys for permission to write to the bucket, assign this policy a new IAM user, and <a href=\"https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey\">create an access key</a> for this user. If you plan to use an IAM role, <a href=\"https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create.html\">create an IAM role</a> and attach the previously defined policy to the role.</p>\n<h3><a href=\"https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3#deploy-the-recorder-node\">Deploy the recorder node</a></h3>\n<p>Deploying a recorder node with S3 as the storage backend is similar to the standard filesystem deployment. You'll need to add your IAM credentials or role, and the S3 bucket information.</p>\n<h4><a href=\"https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3#deploy-with-aws-access-keys\">Deploy with AWS access keys</a></h4>\n<p>For AWS access keys, specify the AWS access key and secret key:</p>\n<pre><code class=\"language-shell\">docker run --name tsrecorder --rm -it \\\r\n  -e TS_AUTHKEY=$TS_AUTHKEY \\\r\n  -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY \\\r\n  -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \\\r\n  -v $HOME/tsrecorder:/data \\\r\n  tailscale/tsrecorder:stable \\\r\n  /tsrecorder --dst='s3://s3.us-east-2.amazonaws.com' --statedir=/data/state \\\r\n  --bucket=$S3_BUCKET_NAME --ui \\\n</code></pre>\n<h4><a href=\"https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3#deploy-with-attached-iam-roles\">Deploy with attached IAM roles</a></h4>\n<p>For IAM roles, the credentials are retrieved from the AWS metadata service. You'll need to assign an IAM role (and if using an EC2 instance, an <a href=\"https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html\">IAM instance profile</a>) to the container.</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='s3://s3.us-east-2.amazonaws.com' --statedir=/data/state \\\r\n  --bucket=$S3_BUCKET_NAME --ui \\\n</code></pre>\n<p>If the instance you're provisioning the Docker container is using <a href=\"https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/configuring-instance-metadata-service.html\">Instance Metadata v2</a>, the default <code>PUT</code> response hop limit is 1 (that is, from the EC2 instance to the metadata service). For IAM instance profiles to work with Docker, you must first configure the limit to account for the extra hop through the EC2 instance, <a href=\"https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/configuring-IMDS-existing-instances.html#modify-PUT-response-hop-limit\">so 2 at minimum</a>.</p>\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. The value must begin with <code>s3://</code> if you intend to use S3 storage for storing the recordings. You can alternatively define the value as an environment variable, <code>TSRECORDER_DST</code>.</li>\n<li><code>--statedir</code> Specifies where the recorder should store its internal state. Accepts a local file path.</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 using <code>--statedir</code>.</li>\n</ul>\n</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<p>There are multiple ways to define the AWS IAM credentials needed to connect to an S3 bucket. In order of precedence, they are:</p>\n<ol>\n<li>Specify <code>--access-key</code> and <code>--secret-key</code></li>\n<li>Set <code>AWS_ACCESS_KEY_ID</code> and <code>AWS_SECRET_ACCESS_KEY</code></li>\n<li>Specify none of the above, and be on an EC2 instance with the right permissions.</li>\n</ol>\n<h4><a href=\"https://tailscale.com/docs/features/tailscale-ssh/how-to/session-recording-s3#cloudflare-r2-compatibility\">Cloudflare R2 Compatibility</a></h4>\n<p>Cloudflare R2 Storage provides an <a href=\"https://developers.cloudflare.com/r2/api/s3/api\">S3-compatible API</a>; however, an additional setting is needed due to differences in their implementation. To use Cloudflare R2 as the storage service for session recordings set the environment variable <code>S3_SEND_CONTENT_MD5</code> to <code>true</code> in addition to any environment variables you are already passing.</p>\n<pre><code class=\"language-shell\">S3_SEND_CONTENT_MD5=true\n</code></pre>\n<p><img src=\"https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed\" alt=\"Project Logo\"></p>\n<p>Ask AI</p>\n<p>reCAPTCHA</p>\n<p>Recaptcha requires verification.</p>\n<p>protected by <strong>reCAPTCHA</strong></p>\n"}