Skip to main content
Open Source Solutions

DocuSeal Self-Hosted Document Signing: My Agency Setup

How I deploy DocuSeal as a self-hosted DocuSign alternative: the Compose file, eIDAS reality, audit-trail storage, and when paying DocuSign actually wins.

Published Updated 10 min read

I have been running DocuSeal as the self-hosted e-signature layer for my agency contracts for a couple of years now, and it is the kind of tool that gets quietly stable in the background once you stop fighting it. This DocuSeal self-hosted document signing guide is the actual setup I deploy: two containers, one Postgres database, a reverse proxy in front, and a backup routine that treats the audit trail as seriously as the signed PDFs themselves.

The pitch is simple. DocuSeal is an open-source alternative to DocuSign and HelloSign, and for the standard agency workload of NDAs, MSAs, statements of work, and the occasional contractor agreement, it covers what you actually use. The features the SaaS plans charge a premium for — branding, API access, custom fields — are in the box. What you trade is operational ownership: when the database fills up, that is on you.

Roughly an hour from a hardened server to a working signing portal with TLS, your logo, and the first template uploaded. Most of that hour is reverse-proxy plumbing, not DocuSeal itself.

When DocuSeal makes sense and when DocuSign still wins

Not every agency should self-host their e-signature workflow. The math is more honest than that.

Pay for DocuSign or HelloSign when:

  • You sign fewer than 10 envelopes a month. The DocuSign Personal plan at around 10€ per month is cheaper than running a Postgres instance you have to back up.
  • You need a Qualified Electronic Signature under eIDAS for regulated contracts (mortgages, certain court filings, notarized closings). DocuSeal is a Standard Electronic Signature, which is fine for B2B but not a QES.
  • You depend on integrations DocuSeal does not have: Salesforce CPQ, Workday, Microsoft Dynamics with full envelope sync. The DocuSeal API is solid, but it is not a drop-in for a sales team living inside a vendor CRM.

Self-host DocuSeal when:

For my agency, the line was somewhere around 30 contracts a month. Below that, DocuSign was fine. Above it, the per-envelope cost on the Business Pro tier started feeling absurd for what is, mechanically, sending a PDF through email and recording who clicked what.

Prerequisites

A short list before any of this lands on a server:

  • A hardened Linux host. SSH keys only, no root login, UFW deny-by-default. My Linux server security fundamentals post is the baseline. If the audit trail database is compromised, every signature you collected becomes legally suspect.
  • A reverse proxy with TLS. Nginx Proxy Manager from my Portainer + NPM + Vaultwarden stack works well; Caddy and Traefik are fine alternatives.
  • A real domain name with DNS access. Cloudflare DNS is the pragmatic default.
  • Server sizing. 2GB RAM is the floor. A 2 vCPU / 2GB box handles a small-agency workload comfortably. Budget roughly 1 to 2 MB per signed envelope including audit trail attachments.

I run DocuSeal on a Hetzner CX22 (2 vCPU, 4GB RAM, 40GB disk) shared with a couple of other low-traffic services. That sizing handles a few hundred envelopes a month with the database, the app, and a daily backup container all on the same host.

The DocuSeal Docker Compose file

Here is the Compose file I deploy. The DocuSeal repo keeps the canonical version current; what is below is the one I check into config management for new deployments, with comments where I diverge from the upstream defaults.

version: '3'

services:
  app:
    depends_on:
      postgres:
        condition: service_healthy
    image: docuseal/docuseal:latest
    ports:
      - 3000:3000
    volumes:
      - /srv/docuseal/data:/data
    environment:
      - FORCE_SSL=sign.yourdomain.com
      - DATABASE_URL=postgresql://postgres:CHANGE_ME_STRONG_PASSWORD@postgres:5432/docuseal
    restart: unless-stopped

  postgres:
    image: postgres:16
    volumes:
      - /srv/docuseal/postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: CHANGE_ME_STRONG_PASSWORD
      POSTGRES_DB: docuseal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

The values to change before you bring this up:

  • FORCE_SSL is the public hostname users sign at. Setting it tells DocuSeal to emit secure cookies and redirect HTTP to HTTPS. Match it to the domain you point at the proxy.
  • DATABASE_URL and POSTGRES_PASSWORD must match. Generate a strong password (32+ characters, no shell metacharacters), and resist the urge to commit the Compose file to a public repo with a real password in it. I keep mine in a private config repo and template the secrets in at deploy time.
  • postgres:16 instead of upstream’s postgres:latest. Pinning the major version means a docker compose pull will not silently upgrade you to Postgres 17 the day they cut the new tag, which would require a pg_upgrade you did not plan.
  • /srv/docuseal/... are absolute host paths. Pick a directory you have already snapshotted at the volume layer.

Bring it up:

docker compose -f /srv/docker/docuseal/docker-compose.yml up -d

Then point your reverse proxy at localhost:3000 (or the container name on the same Docker network), set up the TLS cert, and DocuSeal’s setup wizard greets you on the public hostname.

Reverse proxy notes

In Nginx Proxy Manager: add a Proxy Host pointing sign.yourdomain.com to the DocuSeal host on port 3000. Tick “Block Common Exploits” and “Websockets Support” (DocuSeal uses websockets for the live signing UI). SSL tab, request a Let’s Encrypt cert, force SSL, HTTP/2, HSTS. In Advanced, set proxy_read_timeout 300s; so a slow signer on a flaky connection does not time out mid-PDF render.

The eIDAS and ESIGN reality for self-hosted signatures

This is the part that gets misunderstood, and that I have had to explain to more than one client lawyer over the years.

A self-hosted DocuSeal signature is a Standard Electronic Signature under eIDAS (EU regulation 910/2014) and a valid electronic signature under the US ESIGN Act (15 U.S.C. § 7001). For the standard B2B paperwork (NDAs, MSAs, SOWs, contractor agreements, freelance contracts), that level of signature is legally binding and admissible. The same legal status DocuSign and HelloSign give you on their basic tiers.

What makes it binding is not the cryptography. It is the audit trail. The audit trail captures:

  • The signer’s email address and the link they clicked from.
  • The IP address of every action.
  • A timestamp of when each step happened.
  • A hash of the final PDF, so any later alteration is provable.

DocuSeal generates this audit trail automatically and embeds it as the last page of every signed PDF, plus stores the structured record in Postgres. That is the evidentiary package a court looks at if a signature is ever disputed.

What DocuSeal does not give you is a Qualified Electronic Signature under eIDAS. A QES requires a certified trust service provider, a hardware-backed signing certificate (a national ID card with a chip, or a USB token from a QTSP), and a real-world identity verification step. If you are signing mortgage documents, court filings, or EU government tenders that explicitly require a QES, this is not the tool. Use a QTSP-integrated platform.

For a US or EU agency signing the kinds of contracts agencies actually sign, the standard signature is enough. I am not a lawyer; this is the framing my actual lawyer gave me, and it has held up across two GDPR-sensitive jurisdictions.

Backups, the part the install guide skims over

The install guide tells you to set the volume paths and walk away. The install guide is wrong about that.

DocuSeal has two pieces of state you must back up together:

  1. The Postgres database (/srv/docuseal/postgres in the example above). Holds template definitions, signer records, the audit trail, and metadata pointers to the PDFs.
  2. The data volume (/srv/docuseal/data). Holds the actual signed PDF files.

Back up only the database and you have audit records pointing at PDFs that no longer exist. Back up only the data volume and you have signed PDFs with no proof of who signed them or when. Both, taken close together in time, or you do not have a usable backup.

My production backup routine is two parts on the same daily cron:

# 1. Postgres dump
docker exec docuseal-postgres-1 pg_dump -U postgres docuseal \
  | gzip > /backup/docuseal/db-$(date +%F).sql.gz

# 2. Data volume snapshot via rsync to the same backup target
rsync -a --delete /srv/docuseal/data/ /backup/docuseal/data/

Then the entire /backup/docuseal directory is pulled off-site via Borg or restic to a separate provider. Off-site is the rule, not the suggestion. A backup that lives on the same VPS is a copy.

Restoring is the test that matters. Once a quarter, I spin up DocuSeal on a second VPS, restore the latest dump and the data volume, and verify a sample of signed PDFs renders with their audit trail intact. If you have never tested the restore, you do not have a working backup.

Threat model and operational notes

The realistic risks for a self-hosted signing portal are not exotic:

  1. Public exposure of the Postgres port. Default Compose binds Postgres only to the Docker network, which is correct. Do not expose 5432 to the host or the internet. The DB holds every signature you have ever collected.
  2. Weak admin password. DocuSeal supports email-and-password and SSO via SAML/OIDC. If you run Authentik, wire DocuSeal to it and let Authentik enforce hardware-key login. The admin account is the keys to the audit trail.
  3. Email deliverability. DocuSeal sends signing invitations by email. If SMTP is misconfigured and invites land in spam, signers will not sign and you will not know why. Use a dedicated transactional sender (Postmark, Mailgun, or your own Mailcow) with proper SPF, DKIM, and DMARC.
  4. Audit-trail tampering. Anyone with database access can technically rewrite history before the PDF is finalized. This is why the database backup matters: the signed PDF in your client’s inbox is the canonical record, not the row in your DB.

For a small agency, I also enable rate limiting on the proxy (10 requests per second per IP is comfortably above legitimate use), and I monitor the container with Uptime Kuma so I hear about a failed health check before a client does.

Verifying the deployment before you trust it

A short checklist before you send the first real contract through DocuSeal:

  1. Create an internal test template. Send it to a personal email address.
  2. Sign it from a different device on a different network. Confirm the signed PDF arrives, has the audit trail page, and the hash matches.
  3. Open the signed PDF in a separate viewer (not DocuSeal) and verify the form fields render correctly. PDF rendering quirks are the most common reason a contract gets rejected by a counterparty.
  4. Trigger a manual database backup. Restore it onto a second VPS. Verify you can log in and the signed envelope is still there.

Step 4 is the one most people skip. Do it before you have a hundred signed contracts in the database and a tax authority asking for one of them.

What the deal actually is

Self-hosted DocuSeal is not effortless. The deal is a few minutes of operational attention each month, in exchange for a signing portal that costs roughly 5€ a month to run and where every signed PDF, every audit record, and every signer email lives on a server you own.

For an agency sending 30 to 100 contracts a month, that math works out faster than people expect. For a solo consultant sending three NDAs a quarter, it does not, and DocuSign Personal is the right answer.

Pair the stack above with Authentik for SSO on the admin account, Stirling PDF for the prep work behind clean signing templates, and a backup routine that takes the database and the data volume together. That is the spine of a self-hosted signing layer that holds up the day a client asks “do you still have the contract we signed in March?”.

Watch on YouTube

Video walkthrough

Prefer the screen-recording version of this guide? Watch it on YouTube — opens in a new tab so the player only loads when you ask for it.

Frequently Asked Questions

Want this handled, not just understood?

Reading the playbook is one thing. Running it on production at 2am is another. If you'd rather have me run it for you, the door is open.

Apply for Access