diff --git a/.gitignore b/.gitignore
index 4eacba2..696915f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 .env
 .pre-commit-config.yaml
+secrets
diff --git a/modules/cfdyndns.nix b/modules/cfdyndns.nix
new file mode 100644
index 0000000..9dfe61e
--- /dev/null
+++ b/modules/cfdyndns.nix
@@ -0,0 +1,94 @@
+{ config, pkgs, lib, ... }:
+
+# apikeyFile implementation inspired by grafana config
+with lib;
+
+let
+  cfg = config.services.custom.cfdyndns;
+in
+{
+  options = {
+    services.custom.cfdyndns = {
+      enable = mkEnableOption "Cloudflare Dynamic DNS Client";
+
+      email = mkOption {
+        type = types.str;
+        description = ''
+          The email address to use to authenticate to CloudFlare.
+        '';
+      };
+
+      apikey = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        description = ''
+          The API Key to use to authenticate to CloudFlare.
+        '';
+      };
+
+      apikeyFile = mkOption {
+        default = null;
+        type = types.nullOr types.path;
+        description = ''
+          The API Key to use to authenticate to CloudFlare.
+        '';
+      };
+
+      records = mkOption {
+        default = [];
+        example = [ "host.tld" ];
+        type = types.listOf types.str;
+        description = ''
+          The records to update in CloudFlare.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.apikey != null -> cfg.apikeyFile == null;
+        message = "Cannot set both apikey and apikeyFile";
+      }
+    ];
+
+    systemd.services.cfdyndns = {
+      description = "CloudFlare Dynamic DNS Client";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      startAt = "5 minutes";
+      script = ''
+        ${optionalString (cfg.apikey != null) ''
+          export CLOUDFLARE_APIKEY="${cfg.apikey}"
+        ''}
+        ${optionalString (cfg.apikeyFile != null) ''
+          export CLOUDFLARE_APIKEY="$(cat ${escapeShellArg cfg.apikeyFile})"
+        ''}
+        ${pkgs.cfdyndns}/bin/cfdyndns
+      '';
+      serviceConfig = {
+        Type = "simple";
+        User = config.ids.uids.cfdyndns;
+        Group = config.ids.gids.cfdyndns;
+      };
+      environment = {
+        CLOUDFLARE_EMAIL = "${cfg.email}";
+        CLOUDFLARE_RECORDS = "${concatStringsSep "," cfg.records}";
+      };
+    };
+
+    users.users = {
+      cfdyndns = {
+        group = "cfdyndns";
+        uid = config.ids.uids.cfdyndns;
+      };
+    };
+
+    users.groups = {
+      cfdyndns = {
+        gid = config.ids.gids.cfdyndns;
+      };
+    };
+  };
+}
diff --git a/rpi4.nix b/rpi4.nix
index a46effb..fe56583 100644
--- a/rpi4.nix
+++ b/rpi4.nix
@@ -7,6 +7,7 @@
     ./system/nix.nix
     ./system/i18n.nix
     ./services/jellyfin.nix
+    ./modules/cfdyndns.nix
   ];
 
   nixpkgs.config.allowUnfree = true;
@@ -22,6 +23,24 @@
   ];
   hardware.enableAllFirmware = true;
 
+  networking.domain = "home.felschr.com";
+
+  networking.firewall.allowedTCPPorts = [
+    80 443
+  ];
+
+  security.acme = {
+    acceptTerms = true;
+    email = "felschr@pm.me";
+  };
+
+  services.custom.cfdyndns = {
+    enable = true;
+    email = "felschr@pm.me";
+    apikeyFile = "/etc/nixos/secrets/cfdyndns-apikey";
+    records = [ "home.felschr.com" ];
+  };
+
   programs.zsh.enable = true;
 
   services.openssh.enable = true;