diff --git a/playbooks/roles/ansible-cron/tasks/main.yaml b/playbooks/roles/ansible-cron/tasks/main.yaml
index 0385ebdfff..c25dbea5f6 100644
--- a/playbooks/roles/ansible-cron/tasks/main.yaml
+++ b/playbooks/roles/ansible-cron/tasks/main.yaml
@@ -19,3 +19,9 @@
     day: "{{ update_cron_interval.day }}"
     month: "{{ update_cron_interval.month }}"
     weekday: "{{ update_cron_interval.weekday }}"
+
+- name: Setup log rotation
+  include_role:
+    name: logrotate
+  vars:
+    logrotate_file_name: /var/log/ansible/run_all_cron.log
\ No newline at end of file
diff --git a/playbooks/roles/base-server/defaults/main.yaml b/playbooks/roles/base-server/defaults/main.yaml
index dbc13b26c3..0da2e7296e 100644
--- a/playbooks/roles/base-server/defaults/main.yaml
+++ b/playbooks/roles/base-server/defaults/main.yaml
@@ -4,6 +4,7 @@ bastion_public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSLlN41ftgxkNeUi/kATYP
 base_packages:
   - at
   - git
+  - logrotate
   - lvm2
   - ntp
   - openssh-server
diff --git a/playbooks/roles/install-ansible/tasks/main.yaml b/playbooks/roles/install-ansible/tasks/main.yaml
index ff43453699..1806e3258b 100644
--- a/playbooks/roles/install-ansible/tasks/main.yaml
+++ b/playbooks/roles/install-ansible/tasks/main.yaml
@@ -44,3 +44,10 @@
   copy:
     src: openstack.py
     dest: /etc/ansible/inventory_plugins/openstack.py
+
+
+- name: Setup log rotation
+  include_role:
+    name: logrotate
+  vars:
+    logrotate_file_name: /var/log/ansible/ansible.log
\ No newline at end of file
diff --git a/playbooks/roles/logrotate/README.rst b/playbooks/roles/logrotate/README.rst
new file mode 100644
index 0000000000..4250a90d70
--- /dev/null
+++ b/playbooks/roles/logrotate/README.rst
@@ -0,0 +1,43 @@
+Add log rotation file
+
+.. note:: This role does not manage the ``logrotate`` package or
+          configuration directory, and it is assumed to be installed
+          and available.
+
+This role installs a log rotation file in ``/etc/logrotate.d/`` for a
+given file.
+
+For information on the directives see ``logrotate.conf(5)``.  This is
+not an exhaustive list of directives (contributions are welcome).
+
+** Role Variables **
+
+.. zuul:rolevar:: logrotate_file_name
+
+   The log file on disk to rotate
+
+.. zuul:rolevar:: logrotate_config_file_name
+   :default: Unique name based on :zuul:rolevar::`logrotate.logrotate_file_name`
+
+   The name of the configuration file in ``/etc/logrotate.d``
+
+.. zuul:rolevar:: logrotate_compress
+   :default: yes
+
+.. zuul:rolevar:: logrotate_copytruncate
+   :default: yes
+
+.. zuul:rolevar:: logrotate_delaycompress
+   :default: yes
+
+.. zuul:rolevar:: logrotate_missingok
+   :default: yes
+
+.. zuul:rolevar:: logrotate_rotate
+   :default: 7
+
+.. zuul:rolevar:: logrotate_daily
+   :default: yes
+
+.. zuul:rolevar:: logrotate_notifempty
+   :default: yes
diff --git a/playbooks/roles/logrotate/defaults/main.yaml b/playbooks/roles/logrotate/defaults/main.yaml
new file mode 100644
index 0000000000..8f9b815c24
--- /dev/null
+++ b/playbooks/roles/logrotate/defaults/main.yaml
@@ -0,0 +1,7 @@
+logrotate_compress: yes
+logrotate_copytruncate: yes
+logrotate_delaycompress: yes
+logrotate_missingok: yes
+logrotate_rotate: 7
+logrotate_daily: yes
+logrotate_notifempty: yes
\ No newline at end of file
diff --git a/playbooks/roles/logrotate/tasks/main.yaml b/playbooks/roles/logrotate/tasks/main.yaml
new file mode 100644
index 0000000000..f2767d9f65
--- /dev/null
+++ b/playbooks/roles/logrotate/tasks/main.yaml
@@ -0,0 +1,18 @@
+- name: Check for filename
+  fail:
+    msg: Must set logrotate_file_name for logfile to rotate
+  when: logrotate_file_name is not defined
+
+# Hash the full path to avoid any conflicts but remain idempotent.
+# "/var/log/ansible/ansible.log" becomes "ansible.log.37237.conf" for example
+- name: Create a unique config name
+  set_fact:
+    logrotate_generated_config_file_name: "{{ logrotate_file_name | basename }}.{{ (logrotate_file_name|hash('sha1'))[0:5] }}.conf"
+
+- name: 'Install {{ logrotate_file_name }} rotatation config file'
+  template:
+    src: logrotate.conf.j2
+    dest: '/etc/logrotate.d/{{ logrotate_config_file_name|default(logrotate_generated_config_file_name) }}'
+    owner: root
+    group: root
+    mode: 0644
\ No newline at end of file
diff --git a/playbooks/roles/logrotate/templates/logrotate.conf.j2 b/playbooks/roles/logrotate/templates/logrotate.conf.j2
new file mode 100644
index 0000000000..0ed1039997
--- /dev/null
+++ b/playbooks/roles/logrotate/templates/logrotate.conf.j2
@@ -0,0 +1,21 @@
+{{ logrotate_file_name }} {
+{% if logrotate_compress %}
+ compress
+{% endif %}
+{% if logrotate_copytruncate %}
+ copytruncate
+{% endif %}
+{% if logrotate_delaycompress %}
+ delaycompress
+{% endif %}
+{% if logrotate_missingok %}
+ missingok
+{% endif %}
+ rotate {{ logrotate_rotate }}
+{% if logrotate_daily %}
+ daily
+{% endif %}
+{% if logrotate_notifempty %}
+ notifempty
+{% endif %}
+}
diff --git a/testinfra/test_base.py b/testinfra/test_base.py
index de39174d56..f0b7dc5783 100644
--- a/testinfra/test_base.py
+++ b/testinfra/test_base.py
@@ -151,6 +151,22 @@ def test_unattended_upgrades(host):
         assert cfg_file.contains('apply_updates = yes')
 
 
+def test_logrotate(host):
+    '''Check for log rotation configuration files
+
+       The magic number here is [0:5] of the sha1 hash of the full
+       path to the rotated logfile; the role adds this for uniqueness.
+    '''
+    ansible_vars = host.ansible.get_variables()
+    if ansible_vars['inventory_hostname'] == 'bridge.openstack.org':
+        cfg_file = host.file("/etc/logrotate.d/ansible.log.37237.conf")
+        assert cfg_file.exists
+        assert cfg_file.contains('/var/log/ansible/ansible.log')
+        cfg_file = host.file("/etc/logrotate.d/run_all_cron.log.1a953.conf")
+        assert cfg_file.exists
+        assert cfg_file.contains('/var/log/ansible/run_all_cron.log')
+
+
 def test_openstacksdk_config(host):
     ansible_vars = host.ansible.get_variables()
     if ansible_vars['inventory_hostname'] == 'bridge.openstack.org':