diff --git a/roles/ipareplica/README.md b/roles/ipareplica/README.md
index 8d70b45b4f6d31d2e862238e586b632a8c2db76f..fd7388a52a555488b43710d9d7adeae0a43b9b26 100644
--- a/roles/ipareplica/README.md
+++ b/roles/ipareplica/README.md
@@ -114,6 +114,50 @@ Example playbook to setup the IPA client(s) using principal and password from in
     state: present
 ```
 
+Example inventory file to remove a replica from the domain:
+
+```ini
+[ipareplicas]
+ipareplica1.example.com
+
+[ipareplicas:vars]
+ipaadmin_password=MySecretPassword123
+ipareplica_remove_from_domain=true
+```
+
+Example playbook to remove an IPA replica using admin passwords from the domain:
+
+```yaml
+---
+- name: Playbook to remove IPA replica
+  hosts: ipareplica
+  become: true
+
+  roles:
+  - role: ipareplica
+    state: absent
+```
+
+The inventory will enable the removal of the replica (also a replica) from the domain. Additional options are needed if the removal of the replica is resulting in a topology disconnect or if the replica is the last that has a role.
+
+To continue with the removal with a topology disconnect it is needed to set these parameters:
+
+```ini
+ipareplica_ignore_topology_disconnect=true
+ipareplica_remove_on_server=ipareplica2.example.com
+```
+
+To continue with the removal for a replica that is the last that has a role:
+
+```ini
+ipareplica_ignore_last_of_role=true
+```
+
+Be careful with enabling the `ipareplica_ignore_topology_disconnect` and especially `ipareplica_ignore_last_of_role`, the change can not be reverted easily.
+
+The parameters `ipaserver_ignore_topology_disconnect`, `ipaserver_ignore_last_of_role`, `ipaserver_remove_on_server` and `ipaserver_remove_from_domain` can be used instead.
+
+
 Playbooks
 =========
 
@@ -255,6 +299,19 @@ Variable | Description | Required
 `ipareplica_setup_firewalld` | The value defines if the needed services will automatically be openen in the firewall managed by firewalld. (bool, default: true) | no
 `ipareplica_firewalld_zone` | The value defines the firewall zone that will be used. This needs to be an existing runtime and permanent zone. (string) | no
 
+Undeploy Variables (`state`: absent)
+------------------------------------
+
+These settings should only be used if the result is really wanted. The change might not be revertable easily.
+
+Variable | Description | Required
+-------- | ----------- | --------
+`ipareplica_ignore_topology_disconnect` \| `ipaserver_ignore_topology_disconnect` | If enabled this enforces the removal of the replica even if it results in a topology disconnect. Be careful with this setting. (bool) | false
+`ipareplica_ignore_last_of_role` \| `ipaserver_ignore_last_of_role` | If enabled this enforces the removal of the replica even if the replica is the last with one that has a role. Be careful, this might not be revered easily. (bool) | false
+`ipareplica_remove_from_domain` \| `ipaserver_remove_from_domain` | This enables the removal of the replica from the domain additionally to the undeployment. (bool) | false
+`ipareplica_remove_on_server` \| `ipaserver_remove_on_server` | The value defines the replica in the domain that will to be used to remove the replica from the domain if `ipareplica_ignore_topology_disconnect` and `ipareplica_remove_from_domain` are enabled. Without the need to enable `ipareplica_ignore_topology_disconnect`, the value will be automatically detected using the replication agreements of the replica. (string) | false
+
+
 Authors
 =======
 
diff --git a/roles/ipareplica/tasks/uninstall.yml b/roles/ipareplica/tasks/uninstall.yml
index a5998ece0a7a119b00a821e248a28b1068eae7d0..0b065c3b564c901197272f607d4001f6bc909382 100644
--- a/roles/ipareplica/tasks/uninstall.yml
+++ b/roles/ipareplica/tasks/uninstall.yml
@@ -1,37 +1,19 @@
 ---
 # tasks to uninstall IPA replica
 
-- name: Uninstall - Uninstall IPA replica
-  ansible.builtin.command: >
-    /usr/sbin/ipa-server-install
-    --uninstall
-    -U
-    {{ "--ignore-topology-disconnect" if
-       ipareplica_ignore_topology_disconnect | bool else "" }}
-    {{ "--ignore-last-of-role" if ipareplica_ignore_last_of_role | bool
-       else "" }}
-  register: result_uninstall
-  # 2 means that uninstall failed because IPA replica was not configured
-  failed_when: result_uninstall.rc != 0 and "'Env' object
-    has no attribute 'basedn'" not in result_uninstall.stderr
-  # IPA server is not configured on this system" not in
-  #   result_uninstall.stdout_lines
-  changed_when: result_uninstall.rc == 0
-  # until: result_uninstall.rc == 0
-  retries: 2
-  delay: 1
+- name: Set parameters
+  ansible.builtin.set_fact:
+    _ignore_topology_disconnect: "{{ ipaserver_ignore_topology_disconnect | default(ipareplica_ignore_topology_disconnect) | default(omit) }}"
+    _ignore_last_of_role: "{{ ipaserver_ignore_last_of_role | default(ipareplica_ignore_last_of_role) | default(omit) }}"
+    _remove_from_domain: "{{ ipaserver_remove_from_domain | default(ipareplica_remove_from_domain) | default(omit) }}"
+    _remove_on_server: "{{ ipaserver_remove_on_server | default(ipareplica_remove_on_server) | default(omit) }}"
 
-#- name: Uninstall - Remove all replication agreements and data about replica
-#  ansible.builtin.command: >
-#    /usr/sbin/ipa-replica-manage
-#    del
-#    {{ ipareplica_hostname | default(ansible_facts['fqdn']) }}
-#    --force
-#    --password={{ ipadm_password }}
-#  failed_when: False
-#  delegate_to: "{{ groups.ipaserver[0] | default(fail) }}"
-
-#- name: Remove IPA replica packages
-#  ansible.builtin.package:
-#    name: "{{ ipareplica_packages }}"
-#    state: absent
+- name: Uninstall - Uninstall replica
+  ansible.builtin.include_role:
+    name: ipaserver
+  vars:
+    state: absent
+    ipaserver_ignore_topology_disconnect: "{{ _ignore_topology_disconnect | default(false) }}"
+    ipaserver_ignore_last_of_role: "{{ _ignore_last_of_role | default(false) }}"
+    ipaserver_remove_from_domain: "{{ _remove_from_domain | default(false) }}"
+    ipaserver_remove_on_server: "{{ _remove_on_server | default(NULL) }}"
diff --git a/roles/ipaserver/README.md b/roles/ipaserver/README.md
index 18317fb9f0bbaf3357724c0fbeecc1ae4242b009..130be07c7b34f3bdcc837564cc85199690e5dcae 100644
--- a/roles/ipaserver/README.md
+++ b/roles/ipaserver/README.md
@@ -79,7 +79,7 @@ Example playbook to setup the IPA server using admin and dirman passwords from a
     state: present
 ```
 
-Example playbook to unconfigure the IPA client(s) using principal and password from inventory file:
+Example playbook to unconfigure the IPA server using principal and password from inventory file:
 
 ```yaml
 ---
@@ -169,6 +169,48 @@ Server installation step 2: Copy `<ipaserver hostname>-chain.crt` to the IPA ser
 The files can also be copied automatically: Set `ipaserver_copy_csr_to_controller` to true in the server installation step 1 and set `ipaserver_external_cert_files_from_controller` to point to the `chain.crt` file in the server installation step 2.
 
 
+Example inventory file to remove a server from the domain:
+
+```ini
+[ipaserver]
+ipaserver.example.com
+
+[ipaserver:vars]
+ipaadmin_password=MySecretPassword123
+ipaserver_remove_from_domain=true
+```
+
+Example playbook to remove an IPA server using admin passwords from the domain:
+
+```yaml
+---
+- name: Playbook to remove IPA server
+  hosts: ipaserver
+  become: true
+
+  roles:
+  - role: ipaserver
+    state: absent
+```
+
+The inventory will enable the removal of the server (also a replica) from the domain. Additional options are needed if the removal of the server/replica is resulting in a topology disconnect or if the server/replica is the last that has a role.
+
+To continue with the removal with a topology disconnect it is needed to set these parameters:
+
+```ini
+ipaserver_ignore_topology_disconnect=true
+ipaserver_remove_on_server=ipaserver2.example.com
+```
+
+To continue with the removal for a server that is the last that has a role:
+
+```ini
+ipaserver_ignore_last_of_role=true
+```
+
+Be careful with enabling the `ipaserver_ignore_topology_disconnect` and especially `ipaserver_ignore_last_of_role`, the change can not be reverted easily.
+
+
 Playbooks
 =========
 
@@ -305,6 +347,19 @@ Variable | Description | Required
 `ipaserver_external_cert_files_from_controller` | Files containing the IPA CA certificates and the external CA certificate chains on the controller that will be copied to the ipaserver host to `/root` folder. (list of string) | no
 `ipaserver_copy_csr_to_controller` | Copy the generated CSR from the ipaserver to the controller as `"{{ inventory_hostname }}-ipa.csr"`. (bool) | no
 
+Undeploy Variables (`state`: absent)
+------------------------------------
+
+These settings should only be used if the result is really wanted. The change might not be revertable easily.
+
+Variable | Description | Required
+-------- | ----------- | --------
+`ipaserver_ignore_topology_disconnect` | If enabled this enforces the removal of the server even if it results in a topology disconnect. Be careful with this setting. (bool) | false
+`ipaserver_ignore_last_of_role` | If enabled this enforces the removal of the server even if the server is the last with one that has a role. Be careful, this might not be revered easily. (bool) | false
+`ipaserver_remove_from_domain` | This enables the removal of the server from the domain additionally to the undeployment. (bool) | false
+`ipaserver_remove_on_server` | The value defines the server/replica in the domain that will to be used to remove the server/replica from the domain if `ipaserver_ignore_topology_disconnect` and `ipaserver_remove_from_domain` are enabled. Without the need to enable `ipaserver_ignore_topology_disconnect`, the value will be automatically detected using the replication agreements of the server/replica. (string) | false
+
+
 Authors
 =======
 
diff --git a/roles/ipaserver/defaults/main.yml b/roles/ipaserver/defaults/main.yml
index 6abcb796fbb22f95d8d141247e8c407201404401..5af85c2d4473a387991fc02d673651e8b4a91f78 100644
--- a/roles/ipaserver/defaults/main.yml
+++ b/roles/ipaserver/defaults/main.yml
@@ -42,3 +42,4 @@ ipaserver_copy_csr_to_controller: no
 ### uninstall ###
 ipaserver_ignore_topology_disconnect: no
 ipaserver_ignore_last_of_role: no
+ipaserver_remove_from_domain: false
diff --git a/roles/ipaserver/library/ipaserver_get_connected_server.py b/roles/ipaserver/library/ipaserver_get_connected_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..910104bbb4d516bf6e1971002f64700ad2fed5f3
--- /dev/null
+++ b/roles/ipaserver/library/ipaserver_get_connected_server.py
@@ -0,0 +1,264 @@
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Copyright (C) 2019-2022 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from __future__ import (absolute_import, division, print_function)
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {
+    "metadata_version": "1.0",
+    "supported_by": "community",
+    "status": ["preview"],
+}
+
+DOCUMENTATION = """
+---
+module: ipaserver_get_connected_server
+short_description: Get connected servers for server
+description: Get connected servers for server
+options:
+  ipaadmin_principal:
+    description: The admin principal.
+    default: admin
+    type: str
+  ipaadmin_password:
+    description: The admin password.
+    required: true
+    type: str
+  hostname:
+    description: The FQDN server name.
+    type: str
+    required: true
+author:
+  - Thomas Woerner (@t-woerner)
+"""
+
+EXAMPLES = """
+"""
+
+RETURN = """
+server:
+  description: Connected server name
+  returned: always
+  type: str
+"""
+
+import os
+import tempfile
+import shutil
+from contextlib import contextmanager
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_text
+from ansible.module_utils import six
+
+try:
+    from ipalib import api
+    from ipalib import errors as ipalib_errors  # noqa
+    from ipalib.config import Env
+    from ipaplatform.paths import paths
+    from ipapython.ipautil import run
+    from ipalib.constants import DEFAULT_CONFIG
+    try:
+        from ipalib.install.kinit import kinit_password
+    except ImportError:
+        from ipapython.ipautil import kinit_password
+except ImportError as _err:
+    MODULE_IMPORT_ERROR = str(_err)
+else:
+    MODULE_IMPORT_ERROR = None
+
+
+if six.PY3:
+    unicode = str
+
+
+def temp_kinit(principal, password):
+    """Kinit with password using a temporary ccache."""
+    ccache_dir = tempfile.mkdtemp(prefix='krbcc')
+    ccache_name = os.path.join(ccache_dir, 'ccache')
+
+    try:
+        kinit_password(principal, password, ccache_name)
+    except RuntimeError as e:
+        raise RuntimeError("Kerberos authentication failed: %s" % str(e))
+
+    os.environ["KRB5CCNAME"] = ccache_name
+    return ccache_dir, ccache_name
+
+
+def temp_kdestroy(ccache_dir, ccache_name):
+    """Destroy temporary ticket and remove temporary ccache."""
+    if ccache_name is not None:
+        run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
+        del os.environ['KRB5CCNAME']
+    if ccache_dir is not None:
+        shutil.rmtree(ccache_dir, ignore_errors=True)
+
+
+@contextmanager
+def ipa_connect(module, principal=None, password=None):
+    """
+    Create a context with a connection to IPA API.
+
+    Parameters
+    ----------
+    module: AnsibleModule
+        The AnsibleModule to use
+    principal: string
+        The optional principal name
+    password: string
+        The admin password.
+
+    """
+    if not password:
+        module.fail_json(msg="Password is required.")
+    if not principal:
+        principal = "admin"
+
+    ccache_dir = None
+    ccache_name = None
+    try:
+        ccache_dir, ccache_name = temp_kinit(principal, password)
+        # api_connect start
+        env = Env()
+        env._bootstrap()
+        env._finalize_core(**dict(DEFAULT_CONFIG))
+
+        api.bootstrap(context="server", debug=env.debug, log=None)
+        api.finalize()
+
+        if api.env.in_server:
+            backend = api.Backend.ldap2
+        else:
+            backend = api.Backend.rpcclient
+
+        if not backend.isconnected():
+            backend.connect(ccache=ccache_name)
+        # api_connect end
+    except Exception as e:
+        module.fail_json(msg=str(e))
+    else:
+        try:
+            yield ccache_name
+        except Exception as e:
+            module.fail_json(msg=str(e))
+        finally:
+            temp_kdestroy(ccache_dir, ccache_name)
+
+
+def ipa_command(command, name, args):
+    """
+    Execute an IPA API command with a required `name` argument.
+
+    Parameters
+    ----------
+    command: string
+        The IPA API command to execute.
+    name: string
+        The name parameter to pass to the command.
+    args: dict
+        The parameters to pass to the command.
+
+    """
+    return api.Command[command](name, **args)
+
+
+def _afm_convert(value):
+    if value is not None:
+        if isinstance(value, list):
+            return [_afm_convert(x) for x in value]
+        if isinstance(value, dict):
+            return {_afm_convert(k): _afm_convert(v)
+                    for k, v in value.items()}
+        if isinstance(value, str):
+            return to_text(value)
+
+    return value
+
+
+def module_params_get(module, name):
+    return _afm_convert(module.params.get(name))
+
+
+def host_show(module, name):
+    _args = {
+        "all": True,
+    }
+
+    try:
+        _result = ipa_command("host_show", name, _args)
+    except ipalib_errors.NotFound as e:
+        msg = str(e)
+        if "host not found" in msg:
+            return None
+        module.fail_json(msg="host_show failed: %s" % msg)
+
+    return _result["result"]
+
+
+def main():
+    module = AnsibleModule(
+        argument_spec=dict(
+            ipaadmin_principal=dict(type="str", default="admin"),
+            ipaadmin_password=dict(type="str", required=True, no_log=True),
+            hostname=dict(type="str", required=True),
+        ),
+        supports_check_mode=True,
+    )
+
+    if MODULE_IMPORT_ERROR is not None:
+        module.fail_json(msg=MODULE_IMPORT_ERROR)
+
+    # In check mode always return changed.
+    if module.check_mode:
+        module.exit_json(changed=False)
+
+    ipaadmin_principal = module_params_get(module, "ipaadmin_principal")
+    ipaadmin_password = module_params_get(module, "ipaadmin_password")
+    hostname = module_params_get(module, "hostname")
+
+    server = None
+    right_left = ["iparepltoposegmentrightnode", "iparepltoposegmentleftnode"]
+    with ipa_connect(module, ipaadmin_principal, ipaadmin_password):
+        # At first search in the domain, then ca suffix:
+        #   Search for the first iparepltoposegmentleftnode (node 2), where
+        #   iparepltoposegmentrightnode is hostname (node 1), then for the
+        #   first iparepltoposegmentrightnode (node 2) where
+        #   iparepltoposegmentleftnode is hostname (node 1).
+        for suffix_name in ["domain", "ca"]:
+            for node1, node2 in [[right_left[0], right_left[1]],
+                                 [right_left[1], right_left[0]]]:
+                args = {node1: hostname}
+                result = api.Command.topologysegment_find(
+                    suffix_name, **args)
+                if result and "result" in result and len(result["result"]) > 0:
+                    res = result["result"][0]
+                    if node2 in res:
+                        if len(res[node2]) > 0:
+                            server = res[node2][0]
+                            break
+    if server is not None:
+        module.exit_json(changed=False, server=server)
+    module.exit_json(changed=False)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/roles/ipaserver/tasks/uninstall.yml b/roles/ipaserver/tasks/uninstall.yml
index 7b69f229946e2f7de4641b49b38719aebe75cf60..35ab63567a39d7e667032925ce26f3256a6e7be0 100644
--- a/roles/ipaserver/tasks/uninstall.yml
+++ b/roles/ipaserver/tasks/uninstall.yml
@@ -1,6 +1,47 @@
 ---
 # tasks to uninstall IPA server
 
+- name: Uninstall - Set server hostname for removal
+  ansible.builtin.set_fact:
+    _remove_hostname: "{{ ansible_facts['fqdn'] }}"
+
+- name: Uninstall - Remove server
+  when: ipaserver_remove_from_domain
+  block:
+
+  - name: Uninstall - Fail on missing ipaadmin_password for server removal
+    ansible.builtin.fail:
+      msg: "'ipaadmin_password' is needed for 'ipaserver_remove_from_domain'"
+    when: ipaadmin_password is not defined
+
+  - name: Uninstall - Fail on missing ipaserver_remove_on_server with ipaserver_ignore_topology_disconnect
+    ansible.builtin.fail:
+      msg: "'ipaserver_remove_on_server' is needed for 'ipaserver_remove_from_domain' with 'ipaserver_ignore_topology_disconnect'"
+    when: ipaserver_ignore_topology_disconnect | bool
+          and ipaserver_remove_on_server is not defined
+
+  - name: Uninstall - Get connected server
+    ipaserver_get_connected_server:
+      ipaadmin_principal: "{{ ipaadmin_principal | default('admin') }}"
+      ipaadmin_password: "{{ ipaadmin_password }}"
+      hostname: "{{ _remove_hostname }}"
+    register: result_get_connected_server
+    when: ipaserver_remove_on_server is not defined
+
+  # REMOVE SERVER FROM DOMAIN
+  - name: Uninstall - Server del "{{ _remove_hostname }}"
+    ipaserver:
+      ipaadmin_principal: "{{ ipaadmin_principal | default('admin') }}"
+      ipaadmin_password: "{{ ipaadmin_password }}"
+      name: "{{ _remove_hostname }}"
+      ignore_last_of_role: "{{ ipaserver_ignore_last_of_role }}"
+      ignore_topology_disconnect: "{{ ipaserver_ignore_topology_disconnect }}"
+      # delete_continue: "{{ ipaserver_delete_continue }}"
+      state: absent
+    delegate_to: "{{ ipaserver_remove_on_server | default(result_get_connected_server.server) }}"
+    when: ipaserver_remove_on_server is defined or
+          result_get_connected_server.server is defined
+
 - name: Uninstall - Uninstall IPA server
   ansible.builtin.command: >
     /usr/sbin/ipa-server-install