Jump to content

Dovecot: Difference between revisions

From Official NixOS Wiki
Tie-ling (talk | contribs)
m add link to acme
Upgrade settings to Dovecot 2.4 and new `dovecot2.settings` option
 
(5 intermediate revisions by 2 users not shown)
Line 66: Line 66:
     enablePAM = false;
     enablePAM = false;


     # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_dovecot_lmtp/
     # https://doc.dovecot.org/2.4.4/howto/lmtp/postfix.html
     # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_and_dovecot_sasl/
     # https://doc.dovecot.org/2.4.4/howto/sasl/postfix.html
     extraConfig = ''
     settings = {
       # force to use full user name plus domain name
      # dovecot_config_version and dovecot_storage_version must be explicitly set
       # for disambiguation
       # to retain compatibility with future updates.
       auth_username_format = %Lu
       dovecot_config_version = "2.4.2";
       dovecot_storage_version = "2.4.0";


       # Authentication configuration:
       auth_username_format = "%{user | lower}";
       auth_mechanisms = plain
       auth_mechanisms = [ "plain" ];
       passdb {
       "passdb passwd-file" = {
         driver = passwd-file
         driver = "passwd-file";
         args = ${config.age.secrets.dovecot.path}
         passwd_file_path = config.age.secrets.dovecot.path;
      }
      };
    };
   };
   };
</syntaxhighlight>
</syntaxhighlight>


= Setup virtual users =
= Setup virtual users =
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix"># /var/spool/mail/vmail needs to be created and owned by vmail
  # /var/spool/mail/vmail needs to be created and owned by vmail
   systemd.tmpfiles.rules = [
   users.users."vmail" = {
     "d /var/spool/mail/vmail 0700 vmail vmail -"
     createHome = true;
   ];
    home = "/var/spool/mail/vmail";
   };


   services.dovecot2 = {
   services.dovecot2 = {
     # configure virtual mail user and group
     settings = {
    createMailUser = true;
      # dovecot_config_version and dovecot_storage_version must be explicitly set
    mailUser = "vmail";
      # to retain compatibility with future updates.
    mailGroup = "vmail";
      dovecot_config_version = "2.4.2";
      dovecot_config_version = "2.4.0";
 
      # If config.services.dovecot2.createMailUser is true (which is the default),
      # the user defined in mail_uid and mail_gid will be automatically created.
      mail_uid = "vmail";
      mail_gid = "vmail";
 
      # implement virtual users
      # https://doc.dovecot.org/2.4.4/howto/virtual/simple_install.html
      # store virtual mail under /var/spool/mail/vmail/<DOMAIN>/<USER>/Maildir
      mail_driver = "maildir";
      mail_path = "~/Maildir";


    # implement virtual users
      "namespace inbox" = {
    # https://doc.dovecot.org/2.3/configuration_manual/howto/simple_virtual_install/
        "mailbox All" = { auto = "create"; special_use = "\\All"; };
    # store virtual mail under
        "mailbox Archive" = { auto = "create"; special_use = "\\Archive"; };
    # /var/spool/mail/vmail/<DOMAIN>/<USER>/Maildir/
        "mailbox Drafts" = { auto = "create"; special_use = "\\Drafts"; };
    mailLocation = "maildir:~/Maildir";
        "mailbox Flagged" = { auto = "create"; special_use = "\\Flagged"; };
        "mailbox Junk" = { auto = "create"; special_use = "\\Junk"; autoexpunge = "60d"; };
        "mailbox Sent" = { auto = "create"; special_use = "\\Sent"; };
        "mailbox Trash" = { auto = "create"; special_use = "\\Trash"; autoexpunge = "60d"; };
      };


    extraConfig = ''
       "userdb static" = {
       userdb {
         fields = {
         driver = static
          uid = "vmail";
        # the full e-mail address inside passwd-file is the username (%u)
          gid = "vmail";
        # user@example.com
          username_format = "%u";
        # %d for domain_name %n for user_name
          home = "/var/spool/mail/vmail/%d/%n";
        args = uid=vmail gid=vmail username_format=%u home=/var/spool/mail/vmail/%d/%n
        };
       }
       };
     '';
     };
   };
   };</syntaxhighlight>
</syntaxhighlight>


= Connect to postfix via lmtp =
= Connect to postfix via lmtp =
See [[Postfix]] for further setup on postfix side.
See [[Postfix]] for further setup on postfix side.


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix"> # create path for dovecot socket
  systemd.tmpfiles.rules = [
    "d /var/spool/postfix 0700 postfix postfix -";
  ];
 
   services.dovecot2 = {
   services.dovecot2 = {
     # connection to postfix
     settings = {
    enableLmtp = true;
      # dovecot_config_version and dovecot_storage_version must be explicitly set
      # to retain compatibility with future updates.
      dovecot_config_version = "2.4.2";
      dovecot_config_version = "2.4.0";


    # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_dovecot_lmtp/
      protocols = [ "imap" "lmtp" ];
    # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_and_dovecot_sasl/
 
    extraConfig = ''
      # https://doc.dovecot.org/2.4.4/howto/lmtp/postfix.html
      # connection to postfix via lmtp
       "service lmtp" = {
       service lmtp {
        # Note that the socket needs to be placed here because Postfix access is limited to this directory
      unix_listener /var/spool/postfix/dovecot-lmtp {
        "unix_listener /var/spool/postfix/dovecot-lmtp" = {
        mode = 0600
          user = "postfix";
        user = postfix
          group = "postfix";
        group = postfix
          mode = "0600";
         }
         };
       }
       };
       service auth {
 
         unix_listener /var/spool/postfix/auth {
      # https://doc.dovecot.org/2.4.4/howto/sasl/postfix.html
           mode = 0600
       "service auth" = {
           user = postfix
         "unix_listener /var/spool/postfix/auth" = {
           group = postfix
           user = "postfix";
         }
           group = "postfix";
       }
           mode = "0600";
     '';
         };
  };
       };
     };


   # postfix connection to dovecot
   # postfix connection to dovecot
   services.postfix.config = {
   services.postfix.config = {
     # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_dovecot_lmtp/
     # https://doc.dovecot.org/2.4.4/howto/lmtp/postfix.html
     mailbox_transport = "lmtp:unix:/var/spool/postfix/dovecot-lmtp";
     mailbox_transport = "lmtp:unix:/var/spool/postfix/dovecot-lmtp";
     virtual_transport = "lmtp:unix:/var/spool/postfix/dovecot-lmtp";
     virtual_transport = "lmtp:unix:/var/spool/postfix/dovecot-lmtp";


     # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_and_dovecot_sasl/
     # https://doc.dovecot.org/2.4.4/howto/sasl/postfix.html
     smtpd_sasl_type = "dovecot";
     smtpd_sasl_type = "dovecot";
     smtpd_sasl_path = "/var/spool/postfix/auth";
     smtpd_sasl_path = "/var/spool/postfix/auth";
Line 155: Line 178:
     smtpd_recipient_restrictions = "permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination";
     smtpd_recipient_restrictions = "permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination";
   };
   };
  # create path for dovecot socket
  users.users."postfix" = {
    createHome = true;
    home = "/var/spool/postfix";
  };
</syntaxhighlight>
</syntaxhighlight>


Line 167: Line 183:
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
   services.dovecot2 = {
   services.dovecot2 = {
     sslServerCert = "${sslCertDir}/fullchain.pem";
     settings = {
    sslServerKey = "${sslCertDir}/key.pem";
      # dovecot_config_version and dovecot_storage_version must be explicitly set
    sslCACert = "${sslCertDir}/chain.pem";
      # to retain compatibility with future updates.
      dovecot_config_version = "2.4.2";
      dovecot_config_version = "2.4.0";
 
      # https://doc.dovecot.org/2.4.4/core/config/ssl.html
      ssl = true; # see also "required"
      ssl_server_cert_file = "${sslCertDir}/fullchain.pem";
      ssl_server_key_file = "${sslCertDir}/key.pem";
      ssl_server_ca_file = "${sslCertDir}/chain.pem";
    };  
   };
   };
</syntaxhighlight>
You also need to set proper file permissions on the cert directory and key files.
See [[ACME#Integration_with_service_modules]].
= Using sieve plugins =
The following is required to use Sieve plugins (<code>imap_sieve</code>, ManageSieve, etc):
<syntaxhighlight lang="nix">
environment.systemPackages = with pkgs; [
  dovecot_pigeonhole
];
</syntaxhighlight>
</syntaxhighlight>


= mail_crypt plugin (encryption at rest) =
= mail_crypt plugin (encryption at rest) =
 
{{Outdated}}
The following seems to make mail_crypt work in its per-user/per-folder mode (note that this mode is still described as 'not production quality' in the dovecot docs):
The following seems to make mail_crypt work in its per-user/per-folder mode (note that this mode is still described as 'not production quality' in the dovecot docs):



Latest revision as of 18:08, 14 July 2026

Dovecot is a mail storage server. It handles the storage of e-mail messages and their retrieval by a e-mail client (mail user agent) via authenticated IMAP.

This article describes a basic Dovecot setup with Postfix and virtual users, i.e., e-mail users are configured separately in Dovecot passdb, and are independent from system users.

SSL Certificate with ACME

This article assumes a working ACME configuration for certificate renewal.

{
  config,
  ...
}:
let
  sslCertDir = config.security.acme.certs."example.org".directory;
  domainName = "example.org";
in
{
  # further dovecot configuration goes here
  ...
}

Dovecot passwd database

Create a plaintext virtual user password database in the following format

  # authenticate via Passwd-file
  #
  # bill@example.org:{PLAIN}your_plain_password
  # alice@example.org:{PLAIN-MD5}1a1dc91c907325c69271ddf0c944bc72::::::
  # This file need to have correct permissions.

  age.secrets.dovecot = {
    file = "./dovecot-secret.age";
    # -rw-------
    mode = "600";
    owner = "dovecot2";
    group = "dovecot2";
  };

Open firewall port

Open firewall port for IMAPS clients.

  networking.firewall.allowedTCPPorts = [ 993 ]; # dovecot imaps

Setup auth via passwd-file

  services.dovecot2 = {
    enable = true;

    # use my own passwd file for auth, see below
    enablePAM = false;

    # https://doc.dovecot.org/2.4.4/howto/lmtp/postfix.html
    # https://doc.dovecot.org/2.4.4/howto/sasl/postfix.html
    settings = {
      # dovecot_config_version and dovecot_storage_version must be explicitly set
      # to retain compatibility with future updates.
      dovecot_config_version = "2.4.2";
      dovecot_storage_version = "2.4.0";

      auth_username_format = "%{user | lower}";
      auth_mechanisms = [ "plain" ];
      "passdb passwd-file" = {
        driver = "passwd-file";
        passwd_file_path = config.age.secrets.dovecot.path;
      };
    };
  };

Setup virtual users

# /var/spool/mail/vmail needs to be created and owned by vmail
  systemd.tmpfiles.rules = [
    "d /var/spool/mail/vmail 0700 vmail vmail -"
  ];

  services.dovecot2 = {
    settings = {
      # dovecot_config_version and dovecot_storage_version must be explicitly set
      # to retain compatibility with future updates.
      dovecot_config_version = "2.4.2";
      dovecot_config_version = "2.4.0";

      # If config.services.dovecot2.createMailUser is true (which is the default),
      # the user defined in mail_uid and mail_gid will be automatically created.
      mail_uid = "vmail";
      mail_gid = "vmail";

      # implement virtual users
      # https://doc.dovecot.org/2.4.4/howto/virtual/simple_install.html
      # store virtual mail under /var/spool/mail/vmail/<DOMAIN>/<USER>/Maildir
      mail_driver = "maildir";
      mail_path = "~/Maildir";

      "namespace inbox" = {
        "mailbox All" = { auto = "create"; special_use = "\\All"; };
        "mailbox Archive" = { auto = "create"; special_use = "\\Archive"; };
        "mailbox Drafts" = { auto = "create"; special_use = "\\Drafts"; };
        "mailbox Flagged" = { auto = "create"; special_use = "\\Flagged"; };
        "mailbox Junk" = { auto = "create"; special_use = "\\Junk"; autoexpunge = "60d"; };
        "mailbox Sent" = { auto = "create"; special_use = "\\Sent"; };
        "mailbox Trash" = { auto = "create"; special_use = "\\Trash"; autoexpunge = "60d"; };
      };

      "userdb static" = {
        fields = {
          uid = "vmail";
          gid = "vmail";
          username_format = "%u";
          home = "/var/spool/mail/vmail/%d/%n";
        };
      };
    };
  };

Connect to postfix via lmtp

See Postfix for further setup on postfix side.

  # create path for dovecot socket
  systemd.tmpfiles.rules = [
    "d /var/spool/postfix 0700 postfix postfix -";
  ];

  services.dovecot2 = {
    settings = {
      # dovecot_config_version and dovecot_storage_version must be explicitly set
      # to retain compatibility with future updates.
      dovecot_config_version = "2.4.2";
      dovecot_config_version = "2.4.0";

      protocols = [ "imap" "lmtp" ];

      # https://doc.dovecot.org/2.4.4/howto/lmtp/postfix.html
      "service lmtp" = {
        # Note that the socket needs to be placed here because Postfix access is limited to this directory
        "unix_listener /var/spool/postfix/dovecot-lmtp" = {
          user = "postfix";
          group = "postfix";
          mode = "0600";
        };
      };

      # https://doc.dovecot.org/2.4.4/howto/sasl/postfix.html
      "service auth" = {
        "unix_listener /var/spool/postfix/auth" = {
          user = "postfix";
          group = "postfix";
          mode = "0600";
        };
      };
    };

  # postfix connection to dovecot
  services.postfix.config = {
    # https://doc.dovecot.org/2.4.4/howto/lmtp/postfix.html
    mailbox_transport = "lmtp:unix:/var/spool/postfix/dovecot-lmtp";
    virtual_transport = "lmtp:unix:/var/spool/postfix/dovecot-lmtp";

    # https://doc.dovecot.org/2.4.4/howto/sasl/postfix.html
    smtpd_sasl_type = "dovecot";
    smtpd_sasl_path = "/var/spool/postfix/auth";
    smtpd_sasl_auth_enable = "yes";
    smtpd_recipient_restrictions = "permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination";
  };

Configure SSL Certificates

  services.dovecot2 = {
    settings = {
      # dovecot_config_version and dovecot_storage_version must be explicitly set
      # to retain compatibility with future updates.
      dovecot_config_version = "2.4.2";
      dovecot_config_version = "2.4.0";

      # https://doc.dovecot.org/2.4.4/core/config/ssl.html
      ssl = true; # see also "required"
      ssl_server_cert_file = "${sslCertDir}/fullchain.pem";
      ssl_server_key_file = "${sslCertDir}/key.pem";
      ssl_server_ca_file = "${sslCertDir}/chain.pem";
    }; 
  };

You also need to set proper file permissions on the cert directory and key files. See ACME#Integration_with_service_modules.

Using sieve plugins

The following is required to use Sieve plugins (imap_sieve, ManageSieve, etc):

environment.systemPackages = with pkgs; [
  dovecot_pigeonhole
];

mail_crypt plugin (encryption at rest)

⏲︎︎
This article or section is outdated. Further information might be found in the corresponding discussion. Please remove this notice once the information has been updated.

The following seems to make mail_crypt work in its per-user/per-folder mode (note that this mode is still described as 'not production quality' in the dovecot docs):

security.pam.services.dovecot2 = { }; # needed as we disable PAM below

services.dovecot2 = {
  enable = true;
  enablePAM = false; # need to disable this as we redefine passdb
  mailPlugins.globally.enable = [ "mail_crypt" ]; 
  pluginSettings = {
    mail_crypt_curve = "secp521r1";
    mail_crypt_save_version = "2";
    mail_crypt_require_encrypted_user_key = "yes";
  }; 
  extraConfig = '' 
    mail_attribute_dict = file:%h/.attributes
    userdb {
      driver = passwd
    }  
    passdb {
      driver = pam
      override_fields = userdb_mail_crypt_private_password=%{sha256:password} userdb_mail_crypt_save_version=2
      args = failure_show_msg=yes dovecot2
    }  
  '';
}; 

Troubleshooting

sievec fails to compile basic sieve scripts

Sieve commands such as fileinto need to be enabled explicitly with:

services.dovecot2.sieve.globalExtensions = ["fileinto"];

Otherwise, the sievec command will fail to compile sieve scripts with fileinto statements and as a result the Dovecot service itself will fail to start if the configuration contains services.dovecot2.sieve.scripts.