diff --git a/README.md b/README.md
index 30c9eb1b97e8ec643638a2ae3f770487716b4487..40aa4b76e2a30ab756153b15227ec90e4baaeace 100644
--- a/README.md
+++ b/README.md
@@ -30,4 +30,35 @@ graylog_sidecar_server_url:
 
 # SECRET: token to be used for sidecars
 graylog_sidecar_server_api_token:
+```
+
+## Node Certificates
+
+For node certificates to be generated you will need to create an additional host group
+named `sidecar-ca` with a single host, that stores the CA certificate that should be
+used for client certificate generation.
+
+The CA file must be available at: `/etc/graylog/sidecar/sidecar-ca.pem`
+The CA file's key must be available at: `/etc/graylog/sidecar/sidecar-ca.key`
+
+The location of the files can be configured via variable. The name of the files however
+must be as specified. The following variables are available in regard to the node
+certificates.
+
+```yaml
+# Whether to generate node certificates (default: true)
+generate_node_certs: true
+
+# The local directory where certs are stored before being uploaded
+tmp_cert_dir: "/tmp/graylog-sidecar-certs"
+
+# The path where the CA certificate and key should be fetched from
+# the remote machine specified in the 'sidecar-ca' host group
+gl_sidecar_ca_path: "/etc/graylog/sidecar"
+
+# The directory where client certs should be stored at
+sidecar_cert_dir: "/etc/graylog/sidecar"
+
+# The time in days the client certificates will be valid
+cert_valid_days: 1095
 ```
\ No newline at end of file
diff --git a/defaults/main.yml b/defaults/main.yml
index 173f5cc3d0c028b393f61049b935480971b92f85..5f1d29701882538c66c5c72bc2baeff4d940ef62 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -7,3 +7,10 @@ install_filebeat: true
 filebeat_repo_urls:
   "RedHat": "https://artifacts.elastic.co/packages/oss-8.x/yum"
   "Debian": "https://artifacts.elastic.co/packages/oss-8.x/apt"
+
+# --- OTHER ---
+generate_node_certs: true
+tmp_cert_dir: "/tmp/graylog-sidecar-certs" # local directory
+gl_sidecar_ca_path: "/etc/graylog/sidecar"
+sidecar_cert_dir: "/etc/graylog/sidecar"
+cert_valid_days: 1095
\ No newline at end of file
diff --git a/tasks/main.yml b/tasks/main.yml
index 0ecd2f43ff18f2d0c2989e653c485d15bc4a47ca..5e79877d0fca764df6ee375c23f67ecb1fac46bb 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -65,3 +65,7 @@
     cmd: update-crypto-policies --set DEFAULT
   changed_when: false
   become: true
+
+- name: Generate Node certificates
+  ansible.builtin.import_tasks: node-certs.yml
+  when: generate_node_certs
diff --git a/tasks/node-certs.yml b/tasks/node-certs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..462bb2137a8471aef59b56243e24125ac021a8a2
--- /dev/null
+++ b/tasks/node-certs.yml
@@ -0,0 +1,70 @@
+---
+- name: Fail if 'sidecar-ca' host group is missing # noqa: run_once[task]
+  ansible.builtin.fail:
+    msg: "Please add a host group 'sidecar-ca' with the host storing the CA file first"
+  run_once: true
+  when: "not ({{ groups['sidecar-ca'] is defined }})"
+
+- name: Node Certificates | Create temporary directopry for certificates # noqa: run_once[task]
+  ansible.builtin.file:
+    path: "{{ tmp_cert_dir }}"
+    state: directory
+    mode: 0755
+  run_once: true
+  delegate_to: localhost
+
+- name: Node Certificates | Fetch Sidecar CA Cert
+  ansible.builtin.fetch:
+    src: "{{ item }}"
+    dest: "{{ tmp_cert_dir }}/"
+    flat: true
+  with_items:
+    - "{{ gl_sidecar_ca_path }}/gl-sidecar.pem"
+    - "{{ gl_sidecar_ca_path }}/gl-sidecar.key"
+  delegate_to: "{{ groups['sidecar-ca'] | first }}"
+  become: true
+  run_once: true
+
+- name: Node Certificates
+  delegate_to: localhost
+  block:
+    - name: Node Certificates | Generate private keys
+      community.crypto.openssl_privatekey:
+        path: "{{ tmp_cert_dir }}/sidecar-{{ inventory_hostname }}.key"
+        return_content: true
+        state: present
+
+    - name: Node Certificates | Create CSRs
+      community.crypto.openssl_csr_pipe:
+        privatekey_path: "{{ tmp_cert_dir }}/sidecar-{{ inventory_hostname }}.key"
+        common_name: "{{ ansible_fqdn }}"  # CN
+        subject_alt_name:
+          - "DNS:{{ inventory_hostname }}"
+          - "DNS:{{ ansible_fqdn }}"
+          - "IP:{{ ansible_default_ipv6.address }}"
+          # - "IP:{{ ansible_default_ipv4.address }}"
+      register: "node_csr"
+
+    - name: Node Certificates | Generate Certificates
+      community.crypto.x509_certificate:
+        path: "{{ tmp_cert_dir }}/sidecar-{{ inventory_hostname }}.pem"
+        csr_content: "{{ node_csr.csr }}"
+        provider: ownca
+        ownca_path: "{{ tmp_cert_dir }}/gl-sidecar.pem"
+        ownca_privatekey_path: "{{ tmp_cert_dir }}/gl-sidecar.key"
+        ownca_privatekey_passphrase: "{{ ca_passphrase }}"
+        ownca_not_after: "+{{ cert_valid_days }}d"
+        ownca_not_before: "-1d"  # valid since yesterday
+
+- name: Node Certificates | Copy Certificates
+  become: true
+  block:
+    - name: Node Certificates | Copy Node certificates
+      ansible.builtin.copy:
+        src: "{{ tmp_cert_dir }}/{{ item }}"
+        dest: "{{ sidecar_cert_dir }}"
+        mode: 0600
+      with_items:
+        - "sidecar-{{ inventory_hostname }}.key"
+        - "sidecar-{{ inventory_hostname }}.pem"
+        - "gl-sidecar.pem"
\ No newline at end of file