diff --git a/data/templates/system/watchdog.conf.j2 b/data/templates/system/watchdog.conf.j2
new file mode 100644
index 0000000000..d5f7e5ec4a
--- /dev/null
+++ b/data/templates/system/watchdog.conf.j2
@@ -0,0 +1,5 @@
+### Autogenerated by system_watchdog.py ###
+[Manager]
+RuntimeWatchdogSec={{ timeout }}
+ShutdownWatchdogSec={{ shutdown_timeout }}
+RebootWatchdogSec={{ reboot_timeout }}
diff --git a/interface-definitions/system_watchdog.xml.in b/interface-definitions/system_watchdog.xml.in
new file mode 100644
index 0000000000..c390ad6a9c
--- /dev/null
+++ b/interface-definitions/system_watchdog.xml.in
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+ Hardware watchdog configuration
+ 9999
+
+
+
+
+ Kernel module to load for watchdog device (optional)
+
+ txt
+ Module name (e.g. 'softdog', 'iTCO_wdt', 'sp5100_tco')
+
+
+ [a-zA-Z0-9_\-]+
+
+ Module name must be alphanumeric/underscore/hyphen
+
+
+
+
+ Watchdog timeout for runtime in seconds (1-86400)
+
+ u32:1-86400
+ Seconds
+
+
+
+
+ Timeout must be between 1 and 86400 seconds
+
+ 10
+
+
+
+ Watchdog timeout during shutdown in seconds (1-86400)
+
+ u32:60-86400
+ Seconds
+
+
+
+
+ Shutdown timeout must be between 60 and 86400 seconds
+
+ 120
+
+
+
+ Watchdog timeout during reboot in seconds (60-86400)
+
+ u32:60-86400
+ Seconds
+
+
+
+
+ Reboot timeout must be between 60 and 86400 seconds
+
+ 120
+
+
+
+
+
+
diff --git a/smoketest/scripts/cli/test_system_watchdog.py b/smoketest/scripts/cli/test_system_watchdog.py
new file mode 100644
index 0000000000..5d8b9efbfe
--- /dev/null
+++ b/smoketest/scripts/cli/test_system_watchdog.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+# Copyright VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import os
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.utils.process import cmd
+
+base_path = ['system', 'watchdog']
+
+
+class TestSystemWatchdog(VyOSUnitTestSHIM.TestCase):
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+ super().tearDown()
+
+ def test_enable_watchdog_softdog(self):
+ """Configure watchdog (presence enables) with softdog and check state"""
+ # Presence of 'system watchdog' enables watchdog; set module to softdog
+ self.cli_set(base_path)
+ self.cli_set(base_path + ['module', 'softdog'])
+ self.cli_commit()
+ # Check if softdog module is loaded
+ lsmod = cmd('lsmod')
+ self.assertIn('softdog', lsmod)
+ # Check /dev/watchdog0 exists
+ self.assertTrue(
+ os.path.exists('/dev/watchdog0'), '/dev/watchdog0 does not exist'
+ )
+ # Check systemd config file exists
+ config_path = '/run/systemd/system.conf.d/watchdog.conf'
+ self.assertTrue(
+ os.path.exists(config_path), f"Systemd config file not found: {config_path}"
+ )
+
+ def test_invalid_module_rejected(self):
+ """Verify that a non-existent watchdog module causes commit failure"""
+ # Choose a module name unlikely to exist; include a prefix to avoid collision with real names
+ bogus_module = 'zzzx_watchdog_unit_test_fake'
+ self.cli_set(base_path)
+ self.cli_set(base_path + ['module', bogus_module])
+ # Commit should fail due to verify() raising ConfigError on modprobe dry-run failure
+ with self.assertRaisesRegex(Exception, r"Watchdog module '.*' was not found"):
+ self.cli_commit()
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on())
diff --git a/src/conf_mode/system_watchdog.py b/src/conf_mode/system_watchdog.py
new file mode 100755
index 0000000000..97f9878c24
--- /dev/null
+++ b/src/conf_mode/system_watchdog.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python3
+#
+# Copyright VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from sys import exit
+from pathlib import Path
+
+from vyos.config import Config
+from vyos.template import render
+from vyos.utils.process import call, run
+from vyos import ConfigError
+from vyos import airbag
+
+airbag.enable()
+
+watchdog_config_dir = Path('/run/systemd/system.conf.d')
+watchdog_config_file = Path(watchdog_config_dir / 'watchdog.conf')
+modules_load_directory = Path('/run/modules-load.d')
+modules_load_file = Path(modules_load_directory / 'watchdog.conf')
+WATCHDOG_DEV = Path('/dev/watchdog0')
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['system', 'watchdog']
+
+ if not conf.exists(base):
+ return None
+
+ watchdog = conf.get_config_dict(
+ base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True
+ )
+
+ return watchdog
+
+
+def verify(watchdog):
+ if watchdog is None:
+ return None
+
+ module = watchdog.get('module')
+ device_exists = WATCHDOG_DEV.exists()
+
+ # Require a usable watchdog: either device already present or a module provided
+ if not module and not device_exists:
+ raise ConfigError(
+ "No watchdog device found at /dev/watchdog0 and no module configured. "
+ "Either configure 'system watchdog module ' or ensure the kernel auto-loads a watchdog driver."
+ )
+
+ # If a module is provided, verify it resolves via modprobe (dry-run)
+ if module:
+ # Dry-run modprobe (-n) in quiet mode (-q) verifies availability without loading
+ rc = run(f'modprobe -n -q {module}')
+ if rc != 0:
+ raise ConfigError(
+ f"Watchdog module '{module}' was not found or cannot be loaded"
+ )
+
+ return None
+
+
+def generate(watchdog):
+ # If watchdog node removed entirely, clean up everything
+ if watchdog is None:
+ watchdog_config_file.unlink(missing_ok=True)
+ modules_load_file.unlink(missing_ok=True)
+ return None
+
+ # Persist kernel module autoload on boot if specified (even if not enabled)
+ module = watchdog.get('module')
+ if module:
+ try:
+ modules_load_directory.mkdir(exist_ok=True)
+ modules_load_file.write_text(f"{module}\n")
+ except Exception as e:
+ print(f"Warning: Failed writing modules-load configuration: {e}")
+ else:
+ # If module option removed, drop persisted autoload file
+ modules_load_file.unlink(missing_ok=True)
+
+ # Try to load kernel module if specified and /dev/watchdog0 is missing
+ if not WATCHDOG_DEV.exists():
+ if module:
+ # Try to load the module using vyos call wrapper for logging/airbag integration
+ try:
+ call(f'modprobe {module}')
+ except Exception as e:
+ print(f"Warning: Could not load watchdog module '{module}': {e}")
+ # Re-check for device
+ if not WATCHDOG_DEV.exists():
+ print(
+ "Warning: /dev/watchdog0 not found. Systemd watchdog will not be enabled."
+ )
+ watchdog_config_file.unlink(missing_ok=True)
+ return None
+
+ # Ensure the directory exists
+ watchdog_config_dir.mkdir(parents=True, exist_ok=True)
+
+ # Pass through configured time values directly as seconds
+ render(str(watchdog_config_file), 'system/watchdog.conf.j2', watchdog)
+
+ return None
+
+
+def apply(watchdog):
+ # Reload systemd daemon to apply/unload the watchdog configuration
+ # The watchdog settings take immediate effect after systemd is reloaded
+ call('systemctl daemon-reload')
+
+ return None
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)