bitwarden-backup
Bitwarden automated backup workflow (n8n) that generates a timestamp, triggers a host-side backup script via SSH, bundles the database dump + Bitwarden persisted data into a single archive, uploads the archive to an S3 bucket for offsite storage, cleans up old backups based on retention policy, and sends a Gmail notification with success/failure details (timestamp, file name/size, upload path, and cleanup result).
Shared 1/25/2026
3 views
Visual Workflow
JSON Code
{
"id": "RelmJftnRmU42vRP",
"meta": {
"instanceId": "f08c5d5487792f01b8b222cd5d582f9fe882c4e3ec4a0a4f0e59e4b99cde6c03",
"templateCredsSetupCompleted": true
},
"name": "bitwarden-backup copy",
"tags": [],
"nodes": [
{
"id": "3246010e-a898-4f01-b1c1-e77da1ce2d47",
"name": "When clicking ‘Execute workflow’",
"type": "n8n-nodes-base.manualTrigger",
"position": [
0,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "53874e38-d6b0-4c1c-a992-f6961c7900a9",
"name": "Get current datatime",
"type": "n8n-nodes-base.code",
"position": [
208,
0
],
"parameters": {
"jsCode": "const parts = new Intl.DateTimeFormat('en-AU', {\n timeZone: 'Australia/Melbourne',\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n}).formatToParts(new Date());\n\nconst get = (t) => parts.find(p => p.type === t)?.value;\n\nconst stamp = `${get('year')}${get('month')}${get('day')}T${get('hour')}${get('minute')}${get('second')}Z`;\nreturn [{ stamp }];\n"
},
"typeVersion": 2
},
{
"id": "915cc419-1857-4858-b8a6-cdad1a090463",
"name": "Execute py script to backup data",
"type": "n8n-nodes-base.ssh",
"position": [
416,
0
],
"parameters": {
"cwd": "/home/ubuntu/bitwarden/",
"command": "=python3 /home/ubuntu/bitwarden/bw-backup.py {{ $json.stamp }}",
"authentication": "privateKey"
},
"credentials": {
"sshPrivateKey": {
"id": "rITLa2HqhZbCRRAz",
"name": "ssh to docker host"
}
},
"typeVersion": 1
},
{
"id": "69e2d223-f134-4da6-9d85-7dd3f46658e5",
"name": "Parse the backup output",
"type": "n8n-nodes-base.code",
"position": [
624,
0
],
"parameters": {
"jsCode": "const raw = $json.stdout ?? '';\nlet parsed;\n\ntry {\n parsed = JSON.parse(raw.trim());\n} catch (e) {\n // include stderr/exit code to help debugging\n throw new Error(\n `Failed to parse JSON from stdout.\\nstdout=${raw}\\nstderr=${$json.stderr}\\ncode=${$json.code}`\n );\n}\n\n// Return object for next nodes\nreturn [\n {\n status: parsed.status,\n message: parsed.message,\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "c3490aaf-9e41-49e9-bc4c-58f35414e3c0",
"name": "Send success email",
"type": "n8n-nodes-base.gmail",
"position": [
1712,
-144
],
"webhookId": "81b051ee-8d46-442e-b536-92c1dfa63abf",
"parameters": {
"message": "=<div style=\"font-family: Arial, Helvetica, sans-serif; font-size: 14px; line-height: 1.6; color: #111827;\">\n <h2 style=\"margin: 0 0 12px; font-size: 18px; font-weight: 700; color: #111827;\">\n Bitwarden Backup Completed\n </h2>\n\n <p style=\"margin: 0 0 12px;\">\n Your Bitwarden backup finished successfully at\n <strong>{{ $('Get current datatime').item.json.stamp }}</strong>.\n </p>\n\n <div style=\"background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; padding: 14px 16px; margin: 0 0 14px;\">\n <h3 style=\"margin: 0 0 10px; font-size: 14px; font-weight: 700; color: #111827;\">\n Summary\n </h3>\n\n <table style=\"width: 100%; border-collapse: collapse;\">\n <tr>\n <td style=\"padding: 6px 0; width: 140px; color: #374151;\">Backup file</td>\n <td style=\"padding: 6px 0;\">\n <strong>{{ $('Read files from backup folder').item.json.fileName }}</strong>\n <span style=\"color: #6b7280;\">({{ $('Read files from backup folder').item.json.fileSize }})</span>\n </td>\n </tr>\n <tr>\n <td style=\"padding: 6px 0; width: 140px; color: #374151;\">S3 location</td>\n <td style=\"padding: 6px 0;\">\n <a\n href=\"https://s3-backup.greenhuang.com/bitwarden/{{ $('Read files from backup folder').item.json.fileName }}\"\n style=\"color: #2563eb; text-decoration: none;\"\n >\n https://s3-backup.greenhuang.com/bitwarden/{{ $('Read files from backup folder').item.json.fileName }}\n </a>\n </td>\n </tr>\n </table>\n </div>\n\n <div style=\"background: #fff7ed; border: 1px solid #fed7aa; border-radius: 10px; padding: 14px 16px; margin: 0 0 14px;\">\n <h3 style=\"margin: 0 0 10px; font-size: 14px; font-weight: 700; color: #9a3412;\">\n Cleanup Result\n </h3>\n <div style=\"white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 12px; color: #111827;\">\n{{ $json.stdout }}\n </div>\n </div>\n\n\n</div\n::contentReference[oaicite:0]{index=0}\n",
"options": {},
"subject": "=Your Bitwarden backup plan is success at {{ $('Get current datatime').item.json.stamp }}"
},
"credentials": {
"gmailOAuth2": {
"id": "i7nkZEC6uksd31bA",
"name": "Gmail account"
}
},
"typeVersion": 2.2
},
{
"id": "31eccffe-2128-42fb-953d-279d519b0c33",
"name": "Send error email",
"type": "n8n-nodes-base.gmail",
"position": [
1104,
176
],
"webhookId": "59b1731b-18b4-48ec-bfdd-e21faa449f33",
"parameters": {
"message": "=<div style=\"font-family: Arial, Helvetica, sans-serif; font-size: 14px; line-height: 1.6; color: #111827;\">\n <h2 style=\"margin: 0 0 12px; font-size: 18px; font-weight: 700; color: #b91c1c;\">\n Bitwarden Backup Failed\n </h2>\n\n <p style=\"margin: 0 0 12px;\">\n Your Bitwarden backup workflow failed at\n <strong>{{ $('Get current datatime').item.json.stamp }}</strong>.\n </p>\n\n <div style=\"background: #fef2f2; border: 1px solid #fecaca; border-radius: 10px; padding: 14px 16px; margin: 0 0 14px;\">\n <h3 style=\"margin: 0 0 10px; font-size: 14px; font-weight: 700; color: #991b1b;\">\n Error Details\n </h3>\n\n <div style=\"white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 12px; color: #111827;\">\n{{ $('Parse the backup output').item.json.message }}\n </div>\n </div>\n\n <p style=\"margin: 0; color: #6b7280; font-size: 12px;\">\n Action: Please review the n8n execution logs and the Bitwarden Docker stack status on the host.\n </p>\n</div>\n",
"options": {},
"subject": "=You Bitwarden backup workflow failed at {{ $('Get current datatime').item.json.stamp }}"
},
"credentials": {
"gmailOAuth2": {
"id": "i7nkZEC6uksd31bA",
"name": "Gmail account"
}
},
"typeVersion": 2.2
},
{
"id": "529d4a9d-1dc5-446d-9c09-35e38b23bfe0",
"name": "Read files from backup folder",
"type": "n8n-nodes-base.readWriteFile",
"position": [
1120,
-144
],
"parameters": {
"options": {},
"fileSelector": "=/data/bitwarden-backups/{{ $('Get current datatime').item.json.stamp }}.tar.gz"
},
"typeVersion": 1.1
},
{
"id": "bd930027-69d2-44f2-848b-3c8ca692a0fe",
"name": "Upload a file",
"type": "n8n-nodes-base.s3",
"position": [
1328,
-144
],
"parameters": {
"fileName": "=bitwarden/{{ $json.fileName }}",
"operation": "upload",
"additionalFields": {}
},
"credentials": {
"s3": {
"id": "xfocKHXQxDwkDKyI",
"name": "R2 greenhuang-backup"
}
},
"typeVersion": 1
},
{
"id": "210b65f6-7c12-4868-8db3-42d68d8eac3a",
"name": "Delete old files",
"type": "n8n-nodes-base.ssh",
"position": [
1520,
-144
],
"parameters": {
"cwd": "/home/ubuntu/bitwarden/",
"command": "=python3 /home/ubuntu/bitwarden/delete-old-files.py {{ $('Get current datatime').item.json.stamp }}",
"authentication": "privateKey"
},
"credentials": {
"sshPrivateKey": {
"id": "rITLa2HqhZbCRRAz",
"name": "ssh to docker host"
}
},
"typeVersion": 1
},
{
"id": "e155c70e-0bf8-43b3-9b30-11a7c408d3d1",
"name": "Check the backup status",
"type": "n8n-nodes-base.if",
"position": [
832,
0
],
"parameters": {
"options": {
"ignoreCase": false
},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "7825c5d8-5bc5-499d-80f5-403f35aacc71",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.status }}",
"rightValue": "success"
}
]
}
},
"typeVersion": 2.3
}
],
"active": false,
"pinData": {},
"settings": {
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "6cb36963-1bfc-434e-9146-4ff642bbf770",
"connections": {
"Upload a file": {
"main": [
[
{
"node": "Delete old files",
"type": "main",
"index": 0
}
]
]
},
"Delete old files": {
"main": [
[
{
"node": "Send success email",
"type": "main",
"index": 0
}
]
]
},
"Get current datatime": {
"main": [
[
{
"node": "Execute py script to backup data",
"type": "main",
"index": 0
}
]
]
},
"Check the backup status": {
"main": [
[
{
"node": "Read files from backup folder",
"type": "main",
"index": 0
}
],
[
{
"node": "Send error email",
"type": "main",
"index": 0
}
]
]
},
"Parse the backup output": {
"main": [
[
{
"node": "Check the backup status",
"type": "main",
"index": 0
}
]
]
},
"Read files from backup folder": {
"main": [
[
{
"node": "Upload a file",
"type": "main",
"index": 0
}
]
]
},
"Execute py script to backup data": {
"main": [
[
{
"node": "Parse the backup output",
"type": "main",
"index": 0
}
]
]
},
"When clicking ‘Execute workflow’": {
"main": [
[
{
"node": "Get current datatime",
"type": "main",
"index": 0
}
]
]
}
}
}