How to Install Dehydrated for SSL Certificate Setup

Mar 26, 2026

What Is This Article About?

This guide walks you through installing Dehydrated — a lightweight, beginner-friendly tool that automatically gets you an SSL certificate for your website. SSL certificates are what make your website show the padlock ???? and use HTTPS, keeping your visitors' data safe.

Think of Dehydrated as a helpful assistant that:

  • Talks to a certificate authority (CA) called the ACME server on your behalf
  • Proves you own your domain by temporarily adding a special record to your DNS (Cloudflare)
  • Collects your SSL certificate automatically — no clicking around in dashboards

By the end of this guide, your server will have a working SSL certificate stored and ready to use.

???? Key Terms You'll See In This Guide
ACME Server: The service that issues your SSL certificate (like a digital ID office).
DNS Challenge (dns-01): A way to prove domain ownership by adding a TXT record to your DNS.
EAB Credentials: A special username + password pair to log in to the ACME server.
Hook Script: A helper script that automatically adds/removes DNS records via the Cloudflare API.
Cloudflare API Token: Your permission key to let Dehydrated edit your DNS settings.

Quick Overview

Detail Value
Supported Systems Linux, macOS, WSL (Windows Subsystem for Linux), FreeBSD — basically any Unix-like OS
DNS Provider Used Cloudflare (works with others too, via custom hook scripts)
Validation Method dns-01 — proves domain ownership via a DNS TXT record
ACME Server https://acme.https.in/acme/directory
Why Dehydrated? It is a simple bash script with minimal dependencies — easy to understand and customise

Before You Begin — Prerequisites

Make sure you have the following items ready before starting. Think of this as your packing checklist before a trip.

Software Your System Needs

  • Bash version 4.0 or newer (already installed on most Linux/macOS systems)
  • curl — used to download files and talk to web APIs
  • OpenSSL — used to generate and verify cryptographic keys
  • Standard Unix tools: sed, awk, grep, mktemp (almost always pre-installed)
  • jq — a tool that reads and parses JSON data (needed for the DNS hook script)
  • git — used to download Dehydrated from GitHub
  • Root or sudo access on your server (needed to write files to system directories)

Accounts & Credentials You Need

  • A Cloudflare account with your domain already added
  • A Cloudflare API Token with DNS edit permissions (covered in this guide)
  • EAB Credentials from your ACME server administrator — includes an EAB Key ID and an HMAC Key
???? Tip
If you don't have your EAB credentials yet, contact your ACME server administrator before continuing. You'll need these to register your account in a later step.

Install the Required Dependencies

Open a terminal and run the command for your operating system:

Ubuntu or Debian Linux

sudo apt-get update
sudo apt-get install -y curl openssl jq git

CentOS or RHEL Linux

sudo yum install -y curl openssl jq git

macOS

brew install curl openssl jq git

After running the command, you should see a success message. If any errors appear, resolve them before continuing.

Installing Dehydrated

There are three ways to install Dehydrated. Choose the one that best fits your setup.

OPTION A Install from GitHub — Recommended for Most Users

This is the safest and most common approach. It downloads the full Dehydrated repository to your server.

Step 1 — Go to the /opt directory and download Dehydrated:

cd /opt
sudo git clone https://github.com/dehydrated-io/dehydrated.git
cd dehydrated

Step 2 — Make the script runnable:

sudo chmod +x dehydrated

Step 3 — Create a shortcut so you can run 'dehydrated' from anywhere on your system:

sudo ln -s /opt/dehydrated/dehydrated /usr/local/bin/dehydrated

Step 4 — Confirm the installation worked:

dehydrated --version
? Success Check
You should see a version number like 'v0.7.x'. If you see 'command not found', check that Step 3 ran without errors.
OPTION B Install to Your Home Directory — No Root Access Needed

Use this option if you do not have sudo or root access on the server.

cd ~
git clone https://github.com/dehydrated-io/dehydrated.git
cd dehydrated
chmod +x dehydrated

# Add to PATH so you can use it from any folder
export PATH="$HOME/dehydrated:$PATH"

# Verify installation
./dehydrated --version

To make this permanent (so it works after you log out and back in), add the export line to your ~/.bashrc file:

echo 'export PATH="$HOME/dehydrated:$PATH"' >> ~/.bashrc
source ~/.bashrc
OPTION C Download as a Single Script — Quickest Method

This downloads just the single script file — useful for quick setups or testing.

sudo curl -L https://raw.githubusercontent.com/dehydrated-io/dehydrated/master/dehydrated \
-o /usr/local/bin/dehydrated

sudo chmod +x /usr/local/bin/dehydrated
dehydrated --version

Setting Up the Folder Structure

Dehydrated stores all its files (certificates, keys, settings) in /etc/dehydrated. Let's create these folders now.

Run all of these commands one after another:

# Create the main Dehydrated directory
sudo mkdir -p /etc/dehydrated

# Create sub-folders
sudo mkdir -p /etc/dehydrated/accounts
sudo mkdir -p /etc/dehydrated/certs
sudo mkdir -p /etc/dehydrated/hooks
sudo mkdir -p /etc/dehydrated/chains

# Create a log directory
sudo mkdir -p /var/log/dehydrated

# Set correct permissions (accounts and certs are private)
sudo chmod 755 /etc/dehydrated
sudo chmod 700 /etc/dehydrated/accounts
sudo chmod 700 /etc/dehydrated/certs

After this, your folder layout will look like this:

/etc/dehydrated/
??? accounts/ ? Stores your account keys (keep private!)
??? certs/ ? Where your SSL certificates will be saved
? ??? example.com/
? ??? cert.pem ? Your certificate
? ??? chain.pem ? Intermediate certificate
? ??? fullchain.pem ? cert + chain (use this for web servers)
? ??? privkey.pem ? Your private key (keep secret!)
??? chains/ ? CA certificate chains
??? config ? Main settings file
??? domains.txt ? List of domains you want certificates for
??? hooks/
??? cloudflare.sh ? DNS automation hook script

Creating the Configuration File

The configuration file tells Dehydrated which ACME server to use, where to store files, and how to handle certificates.

Open a new file using the nano text editor:

sudo nano /etc/dehydrated/config

Copy and paste the full configuration below into the file. Read the comments (lines starting with #) to understand what each setting does:

# ?????????????????????????????????????????????????????
# Dehydrated Configuration — Custom ACME Server
# ?????????????????????????????????????????????????????

# ACME SERVER — The service that issues your certificate
CA="https://acme.https.in/acme/directory"

# EAB CREDENTIALS — Uncomment and fill in your credentials
#EAB_KEY_IDENTIFIER="d5a5ac044190a0388e633d492429e789"
#EAB_HMAC_KEY="In73lxoiv_WuLhIlgSznyynM8oBNrkI641IYVmEEOg9"

# Your email — the ACME server will contact you here
CONTACT_EMAIL="your-email@yourdomain.com"

# ?????????????????????????????????????????????????????
# DIRECTORY PATHS
# ?????????????????????????????????????????????????????
BASEDIR="/etc/dehydrated"
ACCOUNTDIR="${BASEDIR}/accounts"
CERTDIR="${BASEDIR}/certs"
CHAINSDIR="${BASEDIR}/chains"
LOCKFILE="${BASEDIR}/lock"

# ?????????????????????????????????????????????????????
# CHALLENGE TYPE — How domain ownership is proved
# ?????????????????????????????????????????????????????
CHALLENGETYPE="dns-01"
HOOK="${BASEDIR}/hooks/cloudflare.sh"
HOOK_CHAIN="yes"

# ?????????????????????????????????????????????????????
# CERTIFICATE SETTINGS
# ?????????????????????????????????????????????????????
KEY_ALGO="rsa"
KEYSIZE="2048"
OCSP_MUST_STAPLE="no"

# ?????????????????????????????????????????????????????
# RENEWAL SETTINGS
# ?????????????????????????????????????????????????????
RENEW_DAYS="30" # Renew 30 days before expiry
PRIVATE_KEY_RENEW="yes"

# ?????????????????????????????????????????????????????
# ADVANCED SETTINGS (safe to leave as defaults)
# ?????????????????????????????????????????????????????
MINIMUM_VALID_DAYS="30"
AUTO_CLEANUP="yes"
IP_VERSION=""
PRIVATE_KEY_ROLLOVER="no"
OPENSSL_CNF=""
CURL_OPTS="--connect-timeout 60 --max-time 600"

# Optional: Enable logging to a file
# LOGFILE="/var/log/dehydrated/dehydrated.log"

Save and close the file: press Ctrl+X, then Y, then Enter.

Now lock down the file so only root can read it:

sudo chmod 600 /etc/dehydrated/config

Setting Up EAB (External Account Binding) Credentials

EAB credentials act like a username and password for your ACME account. You get them from your ACME server administrator. There are three ways to provide them to Dehydrated — choose one:

METHOD 1 In the Configuration File — Recommended

Open your config file and find the EAB section. Remove the # symbols in front of the two EAB lines and fill in your actual credentials:

sudo nano /etc/dehydrated/config

# Find these lines and remove the # from the front:
EAB_KEY_IDENTIFIER="paste-your-key-id-here"
EAB_HMAC_KEY="paste-your-hmac-key-here"
METHOD 2 Using Environment Variables

If you prefer not to save credentials in a file, you can set them as temporary environment variables in your terminal session:

export EAB_KEY_IDENTIFIER="paste-your-key-id-here"
export EAB_HMAC_KEY="paste-your-hmac-key-here"

Note: These will be lost when you close the terminal session.

METHOD 3 As Command-Line Parameters

You can also pass the credentials directly when you run the register command:

dehydrated --register --accept-terms \
--eab-kid "paste-your-key-id-here" \
--eab-hmac-key "paste-your-hmac-key-here"

Creating the Cloudflare DNS Hook Script

This is the script that automatically adds and removes the DNS TXT record needed to prove you own your domain. When Dehydrated needs to verify your domain, it calls this script, which talks to the Cloudflare API on your behalf.

Step 1 — Create the Hook Script File

Open a new file:

sudo nano /etc/dehydrated/hooks/cloudflare.sh

Paste in the entire hook script below:

#!/usr/bin/env bash
# Cloudflare DNS Hook for Dehydrated
# This script is called automatically by Dehydrated during certificate issuance.
# It creates and deletes DNS TXT records using the Cloudflare API.

set -e
set -u
set -o pipefail

# ?? Configuration ??????????????????????????????????
CF_API_TOKEN="${CF_API_TOKEN:-}"
CF_ACCOUNT_ID="${CF_ACCOUNT_ID:-}"
CF_API_URL="https://api.cloudflare.com/client/v4"

# ?? Logging ????????????????????????????????????????
LOG_FILE="${LOG_FILE:-/var/log/dehydrated/cloudflare-hook.log}"
mkdir -p "$(dirname "$LOG_FILE")"

log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "$LOG_FILE" >&2
}

# ?? Get Zone ID from Cloudflare ????????????????????
get_zone_id() {
local domain="$1"
local root_domain
root_domain=$(echo "$domain" | awk -F. '{print $(NF-1)"."$NF}')
local response
response=$(curl -s -X GET "${CF_API_URL}/zones?name=${root_domain}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json")
local zone_id
zone_id=$(echo "$response" | jq -r '.result[0].id // empty')
if [[ -z "$zone_id" ]]; then
error "Failed to get zone ID for ${root_domain}"
return 1
fi
echo "$zone_id"
}

# ?? Create TXT Record ??????????????????????????????
create_txt_record() {
local domain="$1"
local token="$2"
local zone_id
log "Creating TXT record for _acme-challenge.${domain}"
zone_id=$(get_zone_id "$domain")
local response
response=$(curl -s -X POST "${CF_API_URL}/zones/${zone_id}/dns_records" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"TXT\",\"name\":\"_acme-challenge.${domain}\",\"content\":\"${token}\",\"ttl\":120}")
local record_id
record_id=$(echo "$response" | jq -r '.result.id // empty')
if [[ -z "$record_id" ]]; then
error "Failed to create TXT record"
return 1
fi
log "TXT record created (ID: ${record_id})"
echo "$record_id"
}

# ?? Delete TXT Record ??????????????????????????????
delete_txt_record() {
local domain="$1"
local zone_id
log "Deleting TXT record for _acme-challenge.${domain}"
zone_id=$(get_zone_id "$domain")
local response
response=$(curl -s -X GET "${CF_API_URL}/zones/${zone_id}/dns_records?type=TXT&name=_acme-challenge.${domain}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json")
local record_ids
record_ids=$(echo "$response" | jq -r '.result[].id')
for record_id in $record_ids; do
curl -s -X DELETE "${CF_API_URL}/zones/${zone_id}/dns_records/${record_id}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" > /dev/null
log "TXT record deleted (ID: ${record_id})"
done
}

# ?? Wait for DNS to Propagate ??????????????????????
wait_for_propagation() {
local domain="$1"
local token="$2"
local max_attempts=30
local attempt=0
log "Waiting for DNS propagation for _acme-challenge.${domain}"
while [[ $attempt -lt $max_attempts ]]; do
if dig +short TXT "_acme-challenge.${domain}" @1.1.1.1 | grep -q "$token"; then
log "DNS propagation confirmed (attempt $((attempt + 1)))"
return 0
fi
attempt=$((attempt + 1))
log "Not propagated yet (attempt $((attempt))/${max_attempts})"
sleep 10
done
error "DNS propagation timeout"
return 1
}

# ?? Main Hook Handler ??????????????????????????????
main() {
local handler="$1"
shift
case "$handler" in
deploy_challenge)
local domain="$1" token_filename="$2" token_value="$3"
if [[ -z "$CF_API_TOKEN" ]]; then error "CF_API_TOKEN not set"; exit 1; fi
record_id=$(create_txt_record "$domain" "$token_value")
echo "$record_id" > "/tmp/dehydrated_cf_${domain}.txt"
wait_for_propagation "$domain" "$token_value"
;;
clean_challenge)
local domain="$1"
delete_txt_record "$domain"
rm -f "/tmp/dehydrated_cf_${domain}.txt"
;;
deploy_cert)
local domain="$1" keyfile="$2" certfile="$3" fullchainfile="$4" chainfile="$5"
log "Certificate deployed: ${certfile}"
;;
unchanged_cert)
log "Certificate unchanged, no renewal needed"
;;
startup_hook) log 'Dehydrated starting...' ;;
exit_hook) log 'Dehydrated finished' ;;
*) error "Unknown hook: ${handler}" ;;
esac
}

main "$@"

Save and close: Ctrl+X ? Y ? Enter.

Step 2 — Make the Script Executable

The script won't run unless you give it execute permission:

sudo chmod +x /etc/dehydrated/hooks/cloudflare.sh

Verify it has the right permissions:

ls -la /etc/dehydrated/hooks/cloudflare.sh
# You should see: -rwxr-xr-x (the 'x' means executable)

Setting Up Your Cloudflare API Token

Your API token is what lets the hook script talk to Cloudflare. You need to create a scoped token (one with only the permissions it needs — this is safer than using your global API key).

Step 1 — Create a Scoped API Token in Cloudflare

  1. Log in to your Cloudflare dashboard at dash.cloudflare.com
  2. Click on your profile icon (top right) ? select My Profile
  3. Go to the API Tokens tab
  4. Click Create Token
  5. Choose the Edit zone DNS template
  6. Under Permissions, make sure these two are set:
    • Zone ? DNS ? Edit
    • Zone ? Zone ? Read
  7. Under Zone Resources, select: Include ? Specific zone ? choose your domain
  8. Click Continue to summary, then Create Token
  9. ?? IMPORTANT: Copy the token now — Cloudflare only shows it once

Step 2 — Save the Token to a Secure Environment File

Create a file to store your Cloudflare credentials:

sudo nano /etc/dehydrated/cloudflare.env

Add the following, replacing the placeholder values with your actual credentials:

# Cloudflare API Credentials
export CF_API_TOKEN="paste-your-cloudflare-api-token-here"
export CF_ACCOUNT_ID="paste-your-cloudflare-account-id-here"

# Optional: specify a log file for the hook script
export LOG_FILE="/var/log/dehydrated/cloudflare-hook.log"

Save and close, then lock down the file:

sudo chmod 600 /etc/dehydrated/cloudflare.env

Step 3 — Test That Your Token Works

Let's make sure Cloudflare accepts your token before going further:

source /etc/dehydrated/cloudflare.env

curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json"
? Success Check
You should see a JSON response containing "status": "active". If you see an authentication error, double-check that you copied the token correctly.

Registering Your Account with the ACME Server

Now it's time to register your account on the ACME server. This is a one-time step — you only need to do this once per server.

Step 1 — Load Your Configuration

source /etc/dehydrated/config
source /etc/dehydrated/cloudflare.env

Step 2 — Register the Account

Run the following command. It reads your EAB credentials from the config file and accepts the ACME server's terms of service:

dehydrated --register --accept-terms

If your EAB credentials are not in the config file, you can pass them directly:

dehydrated --register --accept-terms \
--eab-kid "your-eab-key-id-here" \
--eab-hmac-key "your-hmac-key-here"

Step 3 — Verify the Account Was Created

Check that account files were created:

ls -la /etc/dehydrated/accounts/
? Success Check
You should see a directory with account key files inside /etc/dehydrated/accounts/. If the folder is empty, registration did not complete — re-read the error output and check your EAB credentials.

Configuring Your Domains

Dehydrated reads a simple text file called domains.txt to know which domains to get certificates for. Each line in the file becomes one certificate.

Create the domains.txt File

sudo nano /etc/dehydrated/domains.txt

Single Domain

yourdomain.com

Domain with Aliases — One Certificate Covers Multiple Names

example.com www.example.com mail.example.com

Multiple Separate Certificates — Each Domain Gets Its Own

example.com www.example.com
api.example.com
mail.example.com

Wildcard Certificate — Covers All Subdomains

*.yourdomain.com yourdomain.com

Organised with Comments

# Production website
example.com www.example.com

# API endpoint
api.example.com

# Staging environment
*.staging.example.com staging.example.com
???? Tip
Put all names that should share one certificate on the same line, separated by spaces. Each new line creates a separate certificate.

Issuing Your First Certificate

This is the big moment! Dehydrated will now contact the ACME server, prove you own your domain via DNS, and collect your certificate.

Step 1 — Load Environment Variables

source /etc/dehydrated/cloudflare.env

Step 2 — Run Dehydrated

The --cron flag tells Dehydrated to check all domains in domains.txt and issue/renew as needed:

dehydrated --cron

If you want to see detailed output of what's happening (recommended for first run):

dehydrated --cron --force --verbose

Other Useful Variants

Issue Certificate for a Specific Domain Only

dehydrated --cron --domain yourdomain.com

Dry Run — Test Without Actually Issuing

dehydrated --cron --dry-run

Force Re-issue Even if Certificate is Still Valid

dehydrated --cron --force

Where Are My Certificate Files?

After a successful run, Dehydrated saves all certificate files in /etc/dehydrated/certs/[your-domain]/

/etc/dehydrated/certs/yourdomain.com/
??? cert.csr ? Certificate Signing Request (created during issuance)
??? cert.pem ? Your certificate (domain certificate only)
??? chain.pem ? Intermediate certificate from the CA
??? fullchain.pem ? cert.pem + chain.pem combined ? USE THIS for web servers
??? privkey.pem ? Your private key ? KEEP THIS SECRET!
??? privkey.pem.old ? Previous key (only exists if key was rotated)
File What It Is & When to Use It
fullchain.pem Use this with your web server (Nginx, Apache). It includes your certificate AND the intermediate chain.
cert.pem Just your certificate. Rarely needed on its own.
privkey.pem Your private key. Treat this like a password — never share it!
chain.pem The intermediate certificate(s) only. Some tools may need this separately.

Verifying Your Certificate

Let's make sure everything worked correctly.

Check Certificate Details

openssl x509 -in /etc/dehydrated/certs/yourdomain.com/cert.pem -noout -text

Check Certificate Expiry Dates

openssl x509 -in /etc/dehydrated/certs/yourdomain.com/cert.pem -noout -dates
# You should see: notBefore=... notAfter=...

Verify the Certificate Chain is Complete

openssl verify -CAfile /etc/dehydrated/certs/yourdomain.com/chain.pem \
/etc/dehydrated/certs/yourdomain.com/cert.pem

Test the Live Certificate on Your Server

echo | openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null \
| openssl x509 -noout -dates

Quick Start Script — Do Everything in One Go

If you want to automate the entire installation and first certificate issuance, you can use this all-in-one script. Edit the variables at the top to match your setup, then run it.

#!/bin/bash
# ?????????????????????????????????????????????????????????
# Dehydrated Quick Start Script
# Edit the variables below, then run: sudo bash quickstart.sh
# ?????????????????????????????????????????????????????????

set -e

# ?? YOUR SETTINGS ??????????????????????????????????????
DOMAIN="yourdomain.com"
ACME_SERVER="https://acme.https.in/acme/directory"
EAB_KEY="paste-your-eab-key-id-here"
HMAC_KEY="paste-your-hmac-key-here"
EMAIL="your-email@yourdomain.com"
CF_TOKEN="paste-your-cloudflare-token-here"
# ???????????????????????????????????????????????????????

export CF_API_TOKEN="$CF_TOKEN"

# Create all necessary directories
mkdir -p /etc/dehydrated/{accounts,certs,hooks,chains}
mkdir -p /var/log/dehydrated

# Set up domains.txt
echo "$DOMAIN" > /etc/dehydrated/domains.txt

# Register account with ACME server (skip if already registered)
if [[ ! -d "/etc/dehydrated/accounts" ]] || [[ -z "$(ls -A /etc/dehydrated/accounts)" ]]; then
echo "Registering account..."
dehydrated --register --accept-terms \
--eab-kid "$EAB_KEY" \
--eab-hmac-key "$HMAC_KEY"
fi

# Issue certificate
echo "Issuing certificate for $DOMAIN..."
dehydrated --cron --force --verbose

echo "Done! Certificate saved to: /etc/dehydrated/certs/${DOMAIN}/"

Save this script, make it executable, and run it:

chmod +x quickstart.sh
sudo bash quickstart.sh

Troubleshooting Common Problems

If something goes wrong, here are the most common issues and how to fix them.

Problem Cause Fix
dehydrated: command not found Script not in PATH or symlink not created Run: export PATH="/usr/local/bin:$PATH" or re-do the symlink step
Hook script not running Missing execute permission on the script file Run: chmod +x /etc/dehydrated/hooks/cloudflare.sh
Cloudflare API error / 403 Forbidden Incorrect API token or wrong permissions Re-verify token with the curl test command and check Zone permissions
EAB registration failed Wrong EAB key ID or HMAC key, or wrong ACME server URL Re-run with --verbose flag. Contact your ACME admin.
DNS propagation timeout Cloudflare updated the record but global DNS is slow Increase the sleep value in wait_for_propagation() in cloudflare.sh

What to Do Next

Congratulations — your SSL certificate is now set up! Here's what to do next to complete your setup:

  • Deploy the Certificate — Configure your web server (Nginx/Apache) to use /etc/dehydrated/certs/yourdomain.com/fullchain.pem and privkey.pem.
  • Set Up Automatic Renewal — Add Dehydrated to a cron job so certificates renew automatically. Typical setup: run dehydrated --cron once per day.
  • Configure Deployment Hooks — Use the deploy_cert hook in cloudflare.sh to automatically restart your web server after a certificate is renewed.
  • Test the Renewal Process — Run dehydrated --cron --dry-run to make sure automatic renewal will work correctly.
  • Set Up Monitoring — Consider monitoring your certificate expiry date with a tool like Nagios or a simple cron email alert.

Installation Checklist

Use this checklist to make sure you haven't missed any steps:

Done

Have any Questions

Call HTTPS

If you have any questions, feel free to call us

© 2025 https.in. All rights reserved.