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
              }
            ]
          ]
        }
      }
    }