diff --git a/ansible/chrony-cleanup.yml b/ansible/chrony-cleanup.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1742a1207f59a9674d77e6b2ac76d8101de261da
--- /dev/null
+++ b/ansible/chrony-cleanup.yml
@@ -0,0 +1,14 @@
+---
+- name: Remove chrony container
+  gather_facts: false
+  hosts:
+    - chrony-server
+    - chrony
+  serial: '{{ kolla_serial|default("0") }}'
+  tags:
+    - chrony
+  tasks:
+    - import_role:
+        name: chrony
+        tasks_from: cleanup.yml
+      when: not enable_chrony | bool
diff --git a/ansible/roles/chrony/tasks/cleanup.yml b/ansible/roles/chrony/tasks/cleanup.yml
new file mode 100644
index 0000000000000000000000000000000000000000..79ef11602ac46f13b8dc4ab3d394ebf33a6a1e3b
--- /dev/null
+++ b/ansible/roles/chrony/tasks/cleanup.yml
@@ -0,0 +1,12 @@
+---
+- name: Stop and remove chrony container
+  become: true
+  kolla_docker:
+    action: "stop_and_remove_container"
+    name: chrony
+
+- name: Remove config for chrony
+  become: true
+  file:
+    path: "{{ node_config_directory }}/chrony"
+    state: "absent"
diff --git a/ansible/roles/prechecks/defaults/main.yml b/ansible/roles/prechecks/defaults/main.yml
index 6361801c83c2d377036b706f0c36fa3fc84e87c5..f05d1116c53481ff2f4ed4063bb066b1b9138aeb 100644
--- a/ansible/roles/prechecks/defaults/main.yml
+++ b/ansible/roles/prechecks/defaults/main.yml
@@ -1,3 +1,6 @@
 ---
 # Whether to enable checks for host OS distribution and release.
 prechecks_enable_host_os_checks: true
+
+# Whether to enable checks for a host NTP daemon.
+prechecks_enable_host_ntp_checks: true
diff --git a/ansible/roles/prechecks/tasks/timesync_checks.yml b/ansible/roles/prechecks/tasks/timesync_checks.yml
index fdbda89c7189b23b864ad79e3641d182174b6692..8e4a7a844d3dac312831c034927871adc9e207f1 100644
--- a/ansible/roles/prechecks/tasks/timesync_checks.yml
+++ b/ansible/roles/prechecks/tasks/timesync_checks.yml
@@ -1,14 +1,71 @@
 ---
-- name: Checking timedatectl status
+# TODO(mgoddard): Remove this check in the Y cycle after chrony has been
+# dropped for a cycle.
+- name: Get container facts
   become: true
-  command: timedatectl status
-  register: timedatectl_status
-  changed_when: false
+  kolla_container_facts:
+    name:
+      - chrony
+  register: container_facts
 
-- name: Fail if the clock is not synchronized
+- name: Fail if chrony container is running
   fail:
     msg: >-
-      timedatectl sees the system clock as unsynchronized.
-      Please wait for synchronization.
+      A chrony container is running, but 'enable_chrony' is 'false'. The chrony
+      container is deprecated from the Wallaby release, and the default value
+      of 'enable_chrony' was changed to 'false'.
+
+      The chrony container may be cleaned up via 'kolla-ansible
+      chrony-cleanup'. You should then install and configure a suitable host
+      NTP daemon before running these prechecks again.
+
+      To continue running the chrony container, set 'enable_chrony' to 'true',
+      however note that this feature will be removed in the Xena release, so it
+      is not recommended for use.
   when:
-    - "'synchronized: yes' not in timedatectl_status.stdout"
+    - "'chrony' in container_facts"
+
+- block:
+    - name: Check for a running host NTP daemon  # noqa command-instead-of-module
+      vars:
+        prechecks_host_ntp_daemons:
+          - chrony
+          - chronyd
+          - ntp
+          - ntpd
+          - systemd-timesyncd
+      become: true
+      command:
+        cmd: "systemctl is-active {{ prechecks_host_ntp_daemons | join(' ') }}"
+      register: systemctl_is_active
+      changed_when: false
+      failed_when: false
+
+    - name: Fail if a host NTP daemon is not running
+      fail:
+        msg: >-
+          No host NTP daemon is running, and the Kolla Ansible chrony container
+          is disabled. Please install and configure a host NTP daemon.
+          Alternatively, set 'prechecks_enable_host_ntp_checks' to 'false' to
+          disable this check if not using one of the following NTP daemons:
+          chrony, ntpd, systemd-timesyncd.
+      when:
+        - systemctl_is_active.rc != 0
+
+    - name: Checking timedatectl status
+      become: true
+      command: timedatectl status
+      register: timedatectl_status
+      changed_when: false
+
+    - name: Fail if the clock is not synchronized
+      fail:
+        msg: >-
+          timedatectl sees the system clock as unsynchronized.
+          Please wait for synchronization.
+          Alternatively, set 'prechecks_enable_host_ntp_checks' to 'false' to
+          disable this check if your NTP daemon is not recognised by
+          'timedatectl status'.
+      when:
+        - "'synchronized: yes' not in timedatectl_status.stdout"
+  when: prechecks_enable_host_ntp_checks | bool
diff --git a/releasenotes/notes/deprecate-chrony-077a8686e79a919e.yaml b/releasenotes/notes/deprecate-chrony-077a8686e79a919e.yaml
index ee8403a9463d88cc30a23363f3a9b26fe22660ad..a0913640f71ed1d579c0380c3e0f279302232011 100644
--- a/releasenotes/notes/deprecate-chrony-077a8686e79a919e.yaml
+++ b/releasenotes/notes/deprecate-chrony-077a8686e79a919e.yaml
@@ -2,8 +2,18 @@
 deprecations:
   - |
     Support for deploying ``chrony`` is deprecated and will be removed in the
-    Xena cycle.
-
+    Xena cycle. The container is no longer enabled by default. To enable it,
+    set ``enable_chrony`` to ``true``.
 upgrade:
   - |
-    Due to deprecation, ``chrony`` is no longer enabled by default.
+    Due to deprecation, ``chrony`` is no longer enabled by default.  To enable
+    it, set ``enable_chrony`` to ``true``.
+
+    If disabled, the container and configuration may be removed by running
+    ``kolla-ansible chrony-cleanup``.
+
+    The ``kolla-ansible prechecks`` command will fail if Chrony is disabled and
+    the container is running. It will also fail if Chrony is disabled and no
+    host NTP daemon is detected. This check may be disabled by setting
+    ``prechecks_enable_host_ntp_checks`` to ``false`` if using an NTP daemon
+    other than chrony, ntpd or systemd-timesyncd.
diff --git a/tests/pre.yml b/tests/pre.yml
index 0a6a9f7eb8c9a0c4d469a321cdb19659e1c12bee..c2f1d2e1a0a68ea61d0774ea786bb967f770b77a 100644
--- a/tests/pre.yml
+++ b/tests/pre.yml
@@ -104,3 +104,31 @@
       until: "'synchronized: yes' in timedatectl_status.stdout"
       retries: 90
       delay: 10
+
+    # TODO(mgoddard): Remove this task in the Y cycle after chrony has been
+    # dropped for a cycle.
+    # NOTE(mgoddard): For upgrades, test the case where we are running
+    # a chrony container, but keep the default of disabled after the
+    # upgrade.
+    - block:
+        - name: Remove host NTP packages
+          become: true
+          package:
+            name:
+              - chrony
+              - ntp
+            state: absent
+
+        # NOTE(mgoddard): removing the systemd-timesyncd package fails, so stop
+        # and disable it instead.
+        - name: Stop systemd-timesyncd service
+          become: true
+          service:
+            name: systemd-timesyncd
+            enabled: no
+            state: stopped
+          when: ansible_os_family == 'Debian'
+      when:
+        - is_upgrade
+        # cephadm gets grumpy without a host-level chrony.
+        - scenario != 'cephadm'
diff --git a/tests/templates/globals-default.j2 b/tests/templates/globals-default.j2
index 65d5c71f7eba2b4ad8a785c4cb12eb98ec24889f..d9f4298071ebc94fcf2319d223e3988cc87f8996 100644
--- a/tests/templates/globals-default.j2
+++ b/tests/templates/globals-default.j2
@@ -33,12 +33,6 @@ enable_openstack_core: "{{ openstack_core_enabled }}"
 enable_horizon: "{{ dashboard_enabled }}"
 enable_heat: "{{ openstack_core_tested }}"
 
-# TODO(yoctozepto): Remove this in the Xena cycle.
-# We have to keep it for now for upgrades because dropping chronyd inbetween
-# will make prechecks fail due to lack of proper host-level timesync (chronyd
-# is containerized and the host-level client either removed or fought with).
-enable_chrony: "no"
-
 {% if scenario != 'bifrost' %}
 kolla_internal_vip_address: "{{ kolla_internal_vip_address }}"
 neutron_external_interface: "{{ neutron_external_interface_name }}"
@@ -128,6 +122,10 @@ glance_backend_ceph: "yes"
 cinder_backend_ceph: "yes"
 nova_backend_ceph: "yes"
 ceph_nova_user: "cinder"
+
+# TODO(yoctozepto): Remove this in the Xena cycle.
+# cephadm doesn't support chrony in a container (checks for chrony.service)
+enable_chrony: "no"
 {% endif %}
 
 {% if tls_enabled %}
diff --git a/tests/upgrade.sh b/tests/upgrade.sh
index 39b6e8333d58bacb26461723271b08b2b27835b8..26a198d9fc35acf289e72461f32a5e434a185f9f 100755
--- a/tests/upgrade.sh
+++ b/tests/upgrade.sh
@@ -13,6 +13,20 @@ function upgrade {
     if [[ "$TLS_ENABLED" = "True" ]]; then
         kolla-ansible -i ${RAW_INVENTORY} -vvv certificates > /tmp/logs/ansible/certificates
     fi
+
+    # TODO(mgoddard): Remove this block in the Y cycle after chrony has been
+    # dropped for a cycle.
+    # NOTE(mgoddard): Remove the chrony container and install a host chrony
+    # daemon.
+    kolla-ansible -i ${RAW_INVENTORY} -vvv chrony-cleanup &> /tmp/logs/ansible/chrony-cleanup
+    if [[ $(source /etc/os-release && echo $ID) = "centos" ]]; then
+        chrony_service="chronyd"
+    else
+        chrony_service="chrony"
+    fi
+    ansible all -i $RAW_INVENTORY -m package -a 'name=chrony state=present' -b &> /tmp/logs/ansible/chrony-install
+    ansible all -i $RAW_INVENTORY -m service -a 'name='$chrony_service' state=started enabled=yes' -b &>> /tmp/logs/ansible/chrony-install
+
     kolla-ansible -i ${RAW_INVENTORY} -vvv prechecks &> /tmp/logs/ansible/upgrade-prechecks
     kolla-ansible -i ${RAW_INVENTORY} -vvv pull &> /tmp/logs/ansible/pull-upgrade
     kolla-ansible -i ${RAW_INVENTORY} -vvv upgrade &> /tmp/logs/ansible/upgrade
diff --git a/tools/kolla-ansible b/tools/kolla-ansible
index 215bcf0ee483ce34ee7ec891d5b4ac3858b06ccf..be9c4747072f220d9b40f222051801472a73aaa5 100755
--- a/tools/kolla-ansible
+++ b/tools/kolla-ansible
@@ -152,6 +152,7 @@ Commands:
     upgrade-bifrost      Upgrades an existing bifrost container
     genconfig            Generate configuration files for enabled OpenStack services
     prune-images         Prune orphaned Kolla images
+    chrony-cleanup       Clean up disabled chrony containers
 EOF
 }
 
@@ -466,6 +467,10 @@ EOF
             exit 1
         fi
         ;;
+(chrony-cleanup)
+        ACTION="Cleanup disabled chrony containers"
+        PLAYBOOK="${BASEDIR}/ansible/chrony-cleanup.yml"
+        ;;
 (bash-completion)
         bash_completion
         exit 0