{"id":98104,"date":"2026-10-14T07:50:00","date_gmt":"2026-10-14T11:50:00","guid":{"rendered":"https:\/\/overcentral.com\/en\/?p=98104"},"modified":"2026-09-29T08:05:27","modified_gmt":"2026-09-29T12:05:27","slug":"emdash-security-self-hosted-fix-98104","status":"publish","type":"post","link":"https:\/\/overcentral.com\/en\/emdash-security-self-hosted-fix-98104\/","title":{"rendered":"EmDash Security Settings for Self-Hosted: The Real Fix"},"content":{"rendered":"<p>Everyone says <a href=\"https:\/\/emDash.com\/\" target=\"_blank\" rel=\"noopener noreferrer\" data-iacss-external=\"1\">EmDash<\/a> is secure because plugins run in sandboxed environments. That\u2019s the headline feature, the reason Cloudflare calls it the spiritual successor to WordPress. And it\u2019s true \u2014 on Cloudflare\u2019s runtime, with a paid plan, every plugin is isolated in a V8 isolate, incapable of touching your database or file system unless you explicitly grant permission.<\/p>\n<p>But that\u2019s not how self-hosting works.<\/p>\n<p><a href=\"https:\/\/overcentral.com\/en\/eu-cra-reporting-requirements-80362\/\" title=\"EU CRA Demands What Shipped and When You Knew\" data-iacss-internal=\"1\">When you<\/a> run EmDash on your own Node.js server \u2014 using SQLite and local storage \u2014 the sandbox disappears. Plugins execute in the same process as the CMS core. The very architecture that makes EmDash secure on Cloudflare is absent. The common advice \u2014 \u201cjust use EmDash, it\u2019s secure by design\u201d \u2014 is dangerously incomplete for anyone running their own instance.<\/p>\n<p>This article is for you if you\u2019re self-hosting. You\u2019ll learn exactly what security settings actually work, which ones don\u2019t, and how to protect your instance when Cloudflare\u2019s sandbox is off the table.<\/p>\n<h2>The Sandbox Myth: Why Self-Hosted EmDash Is Not Automatically Secure<\/h2>\n<p>Cloudflare\u2019s dynamic workers are the engine behind plugin isolation. They spin up a fresh V8 isolate for each plugin execution, enforce capability manifests, and tear down the environment after use. This is a genuine architectural improvement over WordPress\u2019s monolithic plugin model.<\/p>\n<p>But dynamic workers require the <a href=\"https:\/\/www.cloudflare.com\/plans\/developer-platform\/\" target=\"_blank\" rel=\"sponsored noopener noreferrer\" data-iacss-external=\"1\">Cloudflare Workers Paid Plan<\/a> ($5\/month). On the free Cloudflare tier, plugins run in-process \u2014 no sandbox. And on a self-hosted Node.js server, dynamic workers don\u2019t exist at all. The code runs directly in Node.js, with full access to the filesystem, the database connection, and any other resource available to the process.<\/p>\n<p>This isn\u2019t a bug. It\u2019s a design trade-off. EmDash is MIT-licensed and deployable anywhere, but the security model that makes it special is tightly coupled to Cloudflare\u2019s proprietary runtime. If you self-host, you inherit the same risks that plague WordPress plugins: one compromised plugin can read, write, or delete everything.<\/p>\n<p>The common advice \u2014 \u201cEmDash is secure because plugins are sandboxed\u201d \u2014 applies only if you deploy to Cloudflare and pay for the plan. For self-hosters, that advice is misleading.<\/p>\n<h2>Step-by-Step: Securing Your Self-Hosted EmDash Instance<\/h2>\n<p>Below are the concrete steps you need to take. Each step includes the expected outcome and a note on what it does <em>not<\/em> solve.<\/p>\n<h3>Step 1: Enforce HTTPS with a Reverse Proxy<\/h3>\n<p><strong>What to do:<\/strong> Place Nginx or Caddy in front of your EmDash instance. Configure TLS certificates (use Let\u2019s Encrypt via Certbot or Caddy\u2019s automatic HTTPS). Redirect all HTTP traffic to HTTPS.<\/p>\n<p><strong>Expected outcome:<\/strong> All traffic between the browser and your server is encrypted. Passkeys, API tokens, and admin sessions cannot be intercepted in transit.<\/p>\n<p><strong>Why this matters on self-hosted:<\/strong> EmDash <a href=\"https:\/\/overcentral.com\/en\/rascal-does-not-dream-trailer-release-80139\/\" title=\"Rascal Does Not Dream Drops Trailer for Final Film\" data-iacss-internal=\"1\">does not<\/a> include built-in TLS termination. Without a reverse proxy, your admin panel communicates over plain HTTP. Attackers on the same network can sniff credentials.<\/p>\n<p><strong>What it doesn\u2019t fix:<\/strong> It doesn\u2019t isolate plugins or protect against server-side vulnerabilities.<\/p>\n<h3>Step 2: Create a Dedicated Database User with Limited Privileges<\/h3>\n<p><strong>What to do:<\/strong> When setting up EmDash with SQLite, the database file is owned by the Node.js process. If you want to use a separate database server (PostgreSQL via Turso or similar), create a user that can only read\/write the specific tables EmDash needs. Avoid using the database superuser.<\/p>\n<p><strong>Expected outcome:<\/strong> If a plugin or an attacker gains control of the Node process, they cannot drop other databases or modify system tables.<\/p>\n<p><strong>Why this matters on self-hosted:<\/strong> Without dynamic workers, any plugin can execute arbitrary SQL. Limiting database user permissions is your last line of defense.<\/p>\n<p><strong>What it doesn\u2019t fix:<\/strong> The plugin can still read all content and media stored in the database.<\/p>\n<h3>Step 3: Enable Passkey Authentication (and Keep a Backup)<\/h3>\n<p><strong>What to do:<\/strong> During initial setup, EmDash prompts you to create a passkey. Use a password manager (1Password, Bitwarden) to store the passkey. Do not rely on the browser\u2019s local storage only. Also, note that passkeys are device-bound; if you lose the device, you lose access.<\/p>\n<p><strong>Expected outcome:<\/strong> No passwords to leak. Brute-force attacks are impossible because there is no password field.<\/p>\n<p><strong>Why this matters on self-hosted:<\/strong> Passkey authentication works exactly the same on self-hosted as on Cloudflare. It\u2019s one of the few security features that doesn\u2019t depend on the runtime.<\/p>\n<p><strong>What it doesn\u2019t fix:<\/strong> Passkeys protect authentication, not authorization. A plugin with \u201cread content\u201d capability (or no sandbox at all) can still read every post.<\/p>\n<h3>Step 4: Vet Plugins Manually \u2014 No Marketplace Safety Net<\/h3>\n<p><strong>What to do:<\/strong> Before installing any third-party plugin, read its source code. Look for database calls, file writes, external network requests, or eval() usage. Only install plugins from trusted authors with open repositories.<\/p>\n<p><strong>Expected outcome:<\/strong> You reduce the risk of installing malicious or poorly coded plugins.<\/p>\n<p><strong>Why this matters on self-hosted:<\/strong> On Cloudflare, even a malicious plugin is contained by the sandbox. On self-hosted, there is no containment. You are responsible for every line of code you add.<\/p>\n<p><strong>What it doesn\u2019t fix:<\/strong> Zero-day vulnerabilities in trusted plugins still exist. You cannot patch them until the maintainer updates the code.<\/p>\n<h3>Step 5: Store Secrets in Environment Variables, Not the Database<\/h3>\n<p><strong>What to do:<\/strong> Use a <code>.env<\/code>codecodecodecodecode file or your hosting provider\u2019s secret store for API keys, SMTP credentials, and database connection strings. Reference them in your EmDash configuration (e.g., <code>import.meta.env<\/code>codecodecodecodecode). Never hardcode secrets.<\/p>\n<p><strong>Expected outcome:<\/strong> If the database is compromised, secrets remain inaccessible.<\/p>\n<p><strong>Why this matters on self-hosted:<\/strong> EmDash stores configuration in the database by default. A plugin that reads the database can see SMTP passwords and API tokens. Environment variables are outside the database.<\/p>\n<p><strong>What it doesn\u2019t fix:<\/strong> The Node process can still read environment variables. A plugin with full process access can still steal them.<\/p>\n<h3>Step 6: Schedule Regular Backups and Monitor Logs<\/h3>\n<p><strong>What to do:<\/strong> Back up the SQLite database file and the media directory daily. Use a cron job or a backup service. Monitor system logs for unexpected file changes or failed authentication attempts.<\/p>\n<p><strong>Expected outcome:<\/strong> If an incident occurs, you can restore from a known clean state. Logs help you detect breaches early.<\/p>\n<p><strong>Why this matters on self-hosted:<\/strong> There is no automatic rollback or version history for the database (EmDash does track content revisions, but not schema changes). You own the recovery process.<\/p>\n<p><strong>What it doesn\u2019t fix:<\/strong> Backups don\u2019t prevent attacks. They only reduce downtime and data loss.<\/p>\n<h2>Security Features: Cloudflare Deploy vs. Self-Hosted<\/h2>\n<table class=\"mw-table\">\n<thead>\n<tr>\n<th>Feature<\/th>\n<th>Cloudflare (Paid Plan)<\/th>\n<th>Self-Hosted (Node.js)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Plugin sandbox (V8 isolate)<\/td>\n<td>Yes, every plugin runs in its own isolated worker<\/td>\n<td>No, plugins run in-process with full access<\/td>\n<\/tr>\n<tr>\n<td>Capability manifest enforcement<\/td>\n<td>Enforced by dynamic worker runtime<\/td>\n<td>Not enforced \u2014 manifest is advisory only<\/td>\n<\/tr>\n<tr>\n<td>Database access control<\/td>\n<td>Plugin must declare <code>read content<\/code>codecodecodecodecode or <code>write content<\/code>codecodecodecodecode<\/td>\n<td>Plugin can execute any SQL via the Node.js database adapter<\/td>\n<\/tr>\n<tr>\n<td>File system access<\/td>\n<td>Plugin cannot access local storage unless granted (R2)<\/td>\n<td>Plugin can read\/write any file the Node process can access<\/td>\n<\/tr>\n<tr>\n<td>Network egress control<\/td>\n<td>Plugin must declare <code>email send<\/code>codecodecodecodecode or <code>fetch<\/code>codecodecodecodecode<\/td>\n<td>Plugin can make unrestricted network calls<\/td>\n<\/tr>\n<tr>\n<td>Authentication<\/td>\n<td>Passkey + optional password (WebAuthn)<\/td>\n<td>Passkey only (same as Cloudflare, no dependency)<\/td>\n<\/tr>\n<tr>\n<td>TLS termination<\/td>\n<td>Built-in via Cloudflare edge<\/td>\n<td>Requires reverse proxy (Nginx, Caddy)<\/td>\n<\/tr>\n<tr>\n<td>Backup\/recovery<\/td>\n<td>D1 backups via Cloudflare dashboard<\/td>\n<td>Manual backup of SQLite file and media directory<\/td>\n<\/tr>\n<tr>\n<td>Cost<\/td>\n<td>$5\/month + usage-based charges (unpredictable)<\/td>\n<td>Server cost (fixed) + no Cloudflare fees, but no sandbox<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The table makes the trade-off clear: self-hosting saves you from usage-based billing surprises but strips away every security feature that makes EmDash architecturally superior to WordPress.<\/p>\n<h2>When Self-Hosted Security Fails: The Billing Trap Reversed<\/h2>\n<p>Common advice also warns about Cloudflare\u2019s unpredictable billing \u2014 a DDoS attack could rack up thousands in worker invocations. Self-hosting avoids that risk. You pay a flat server fee and your bill stays the same regardless of traffic.<\/p>\n<p>But the security cost is real. Without sandboxed plugins, you are back to the WordPress model of trusting every plugin with full system access. The difference is that WordPress has 60,000 plugins and a decade of community vetting. EmDash has zero production plugins and a two-month-old codebase.<\/p>\n<p>If you self-host, you are betting that no plugin you install contains a vulnerability. History suggests that\u2019s a bad bet.<\/p>\n<h2>Final Recommendation: Who Should Self-Host and Who Shouldn\u2019t<\/h2>\n<p>Self-hosting EmDash makes sense only if you are an experienced developer who can audit every plugin\u2019s source code, maintain strict server hardening, and accept the risk. For anyone else \u2014 bloggers, small businesses, agencies \u2014 the right move is to deploy on Cloudflare\u2019s paid plan. The $5\/month is cheap insurance against the most common attack vector in content management.<\/p>\n<p>EmDash\u2019s security model is not a lie. It is a real innovation. But it is also a lock-in. The same architecture that protects you on Cloudflare leaves you exposed when you walk away.<\/p>\n<p>If <a href=\"https:\/\/overcentral.com\/en\/nba-2k27-settings-change-79695\/\" title=\"10 Settings You Must Change in NBA 2K27 for Better\" data-iacss-internal=\"1\">you must<\/a> self-host, use the six steps above. They are not a replacement for sandboxing. They are a damage-control kit for a CMS that was never designed to run alone.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Everyone says EmDash is secure because plugins run in sandboxed environments. That\u2019s the headline feature, the reason Cloudflare calls it the spiritual successor to WordPress. And it\u2019s true \u2014 on Cloudflare\u2019s runtime, with a paid plan, every plugin is isolated in a V8 isolate, incapable of touching your database or file system unless you explicitly [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":100499,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/98104.png","fifu_image_alt":"EmDash Security Settings for Self-Hosted: The Real Fix","footnotes":""},"categories":[31],"tags":[],"class_list":["post-98104","post","type-post","status-publish","format-standard","has-post-thumbnail","category-technology"],"fifu_image_url":"https:\/\/cards.overcentral.com\/cards\/en\/98104.png","fifu_image_alt":"EmDash Security Settings for Self-Hosted: The Real Fix","_links":{"self":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98104","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/comments?post=98104"}],"version-history":[{"count":1,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98104\/revisions"}],"predecessor-version":[{"id":100500,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/posts\/98104\/revisions\/100500"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media\/100499"}],"wp:attachment":[{"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/media?parent=98104"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/categories?post=98104"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/overcentral.com\/en\/wp-json\/wp\/v2\/tags?post=98104"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}