diff --git a/README-user.md b/README-user.md
index e51d7af300afb6577b569f1370a396d75cf271a9..b80bf39d186ccafc9532d9e668fa2aca2bd59056 100644
--- a/README-user.md
+++ b/README-user.md
@@ -279,7 +279,6 @@ Example playbook to disable a user:
 
 This can also be done as an alternative with the `users` variable containing only names.
 
-
 Example playbook to enable users:
 
 ```yaml
@@ -298,6 +297,22 @@ Example playbook to enable users:
 
 This can also be done as an alternative with the `users` variable containing only names.
 
+Example playbook to rename users:
+
+```yaml
+---
+- name: Playbook to handle users
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  # Rename user pinky to reddy
+  - ipauser:
+      ipaadmin_password: SomeADMINpassword
+      name: pinky
+      rename: reddy
+      state: enabled
+```
 
 Example playbook to unlock users:
 
@@ -401,7 +416,7 @@ Variable | Description | Required
 `update_password` | Set password for a user in present state only on creation or always. It can be one of `always` or `on_create` and defaults to `always`. | no
 `preserve` | Delete a user, keeping the entry available for future use. (bool)  | no
 `action` | Work on user or member level. It can be on of `member` or `user` and defaults to `user`. | no
-`state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `unlocked` or `undeleted`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes
+`state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `renamed`, `unlocked` or `undeleted`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes
 
 
 
@@ -458,10 +473,10 @@ Variable | Description | Required
 `smb_profile_path:` \| `ipantprofilepath` | SMB profile path, in UNC format. Requires FreeIPA version 4.8.0+. | no 
 `smb_home_dir` \| `ipanthomedirectory` | SMB Home Directory, in UNC format. Requires FreeIPA version 4.8.0+.  | no 
 `smb_home_drive` \| `ipanthomedirectorydrive` | SMB Home Directory Drive, a single upercase letter (A-Z) followed by a colon (:), for example "U:". Requires FreeIPA version 4.8.0+. | no 
+`rename` \| `new_name` | Rename the user object to the new name string. Only usable with `state: renamed`. | no
 `nomembers` | Suppress processing of membership attributes. (bool) | no
 
 
-
 Return Values
 =============
 
@@ -477,5 +492,5 @@ Variable | Description | Returned When
 Authors
 =======
 
-Thomas Woerner
-Rafael Jeffman
+- Thomas Woerner
+- Rafael Jeffman
diff --git a/plugins/modules/ipauser.py b/plugins/modules/ipauser.py
index db39cc7b1d3fd197cc580434c69fed5aa105a283..a1217456d2fa96609c2d7534c34ecb7507271fbe 100644
--- a/plugins/modules/ipauser.py
+++ b/plugins/modules/ipauser.py
@@ -319,6 +319,11 @@ options:
         description: Suppress processing of membership attributes
         required: false
         type: bool
+      rename:
+        description: Rename the user object
+        required: false
+        type: str
+        aliases: ["new_name"]
     required: false
   first:
     description: The first name. Required if user does not exist.
@@ -586,6 +591,11 @@ options:
     description: Suppress processing of membership attributes
     required: false
     type: bool
+  rename:
+    description: Rename the user object
+    required: false
+    type: str
+    aliases: ["new_name"]
   preserve:
     description: Delete a user, keeping the entry available for future use
     required: false
@@ -607,7 +617,8 @@ options:
     default: present
     choices: ["present", "absent",
               "enabled", "disabled",
-              "unlocked", "undeleted"]
+              "unlocked", "undeleted",
+              "renamed"]
 author:
   - Thomas Woerner (@t-woerner)
 """
@@ -694,6 +705,13 @@ EXAMPLES = """
     smb_profile_path: \\\\server\\profiles\\some_profile
     smb_home_dir: \\\\users\\home\\smbuser
     smb_home_drive: "U:"
+
+# Rename an existing user
+- ipauser:
+    ipaadmin_password: SomeADMINpassword
+    name: someuser
+    rename: anotheruser
+    state: renamed
 """
 
 RETURN = """
@@ -857,7 +875,7 @@ def check_parameters(  # pylint: disable=unused-argument
         employeenumber, employeetype, preferredlanguage, certificate,
         certmapdata, noprivate, nomembers, preserve, update_password,
         smb_logon_script, smb_profile_path, smb_home_dir, smb_home_drive,
-        idp, ipa_user_id,
+        idp, ipa_user_id, rename
 ):
     if state == "present" and action == "user":
         invalid = ["preserve"]
@@ -885,6 +903,19 @@ def check_parameters(  # pylint: disable=unused-argument
             module.fail_json(
                 msg="Preserve is only possible for state=absent")
 
+    if state != "renamed":
+        invalid.append("rename")
+    else:
+        invalid.extend([
+            "preserve", "principal", "manager", "certificate", "certmapdata",
+        ])
+        if not rename:
+            module.fail_json(
+                msg="A value for attribute 'rename' must be provided.")
+        if action == "member":
+            module.fail_json(
+                msg="Action member can not be used with state: renamed.")
+
     module.params_fail_used_invalid(invalid, state, action)
 
     if certmapdata is not None:
@@ -1097,6 +1128,8 @@ def main():
         idp=dict(type="str", default=None, aliases=['ipaidpconfiglink']),
         idp_user_id=dict(type="str", default=None,
                          aliases=['ipaidpconfiglink']),
+        rename=dict(type="str", required=False, default=None,
+                    aliases=["new_name"]),
     )
 
     ansible_module = IPAAnsibleModule(
@@ -1128,7 +1161,7 @@ def main():
                         choices=["member", "user"]),
             state=dict(type="str", default="present",
                        choices=["present", "absent", "enabled", "disabled",
-                                "unlocked", "undeleted"]),
+                                "unlocked", "undeleted", "renamed"]),
 
             # Add user specific parameters for simple use case
             **user_spec
@@ -1209,6 +1242,8 @@ def main():
     preserve = ansible_module.params_get("preserve")
     # mod
     update_password = ansible_module.params_get("update_password")
+    # rename
+    rename = ansible_module.params_get("rename")
     # general
     action = ansible_module.params_get("action")
     state = ansible_module.params_get("state")
@@ -1219,27 +1254,30 @@ def main():
        (users is None or len(users) < 1):
         ansible_module.fail_json(msg="One of name and users is required")
 
-    if state == "present":
+    if state in ["present", "renamed"]:
         if names is not None and len(names) != 1:
+            act = "renamed" if state == "renamed" else "added"
             ansible_module.fail_json(
-                msg="Only one user can be added at a time using name.")
-
-    check_parameters(
-        ansible_module, state, action,
-        first, last, fullname, displayname, initials, homedir, gecos, shell,
-        email,
-        principal, principalexpiration, passwordexpiration, password, random,
-        uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
-        manager, carlicense, sshpubkey, userauthtype, userclass, radius,
-        radiususer, departmentnumber, employeenumber, employeetype,
-        preferredlanguage, certificate, certmapdata, noprivate, nomembers,
-        preserve, update_password, smb_logon_script, smb_profile_path,
-        smb_home_dir, smb_home_drive, idp, idp_user_id)
-    certmapdata = convert_certmapdata(certmapdata)
+                msg="Only one user can be %s at a time using name." % (act))
 
     # Use users if names is None
     if users is not None:
         names = users
+    else:
+        check_parameters(
+            ansible_module, state, action,
+            first, last, fullname, displayname, initials, homedir, gecos,
+            shell, email,
+            principal, principalexpiration, passwordexpiration, password,
+            random,
+            uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
+            manager, carlicense, sshpubkey, userauthtype, userclass, radius,
+            radiususer, departmentnumber, employeenumber, employeetype,
+            preferredlanguage, certificate, certmapdata, noprivate, nomembers,
+            preserve, update_password, smb_logon_script, smb_profile_path,
+            smb_home_dir, smb_home_drive, idp, idp_user_id, rename,
+        )
+        certmapdata = convert_certmapdata(certmapdata)
 
     # Init
 
@@ -1330,6 +1368,7 @@ def main():
                 smb_home_drive = user.get("smb_home_drive")
                 idp = user.get("idp")
                 idp_user_id = user.get("idp_user_id")
+                rename = user.get("rename")
                 certificate = user.get("certificate")
                 certmapdata = user.get("certmapdata")
                 noprivate = user.get("noprivate")
@@ -1346,7 +1385,8 @@ def main():
                     employeetype, preferredlanguage, certificate,
                     certmapdata, noprivate, nomembers, preserve,
                     update_password, smb_logon_script, smb_profile_path,
-                    smb_home_dir, smb_home_drive, idp, idp_user_id)
+                    smb_home_dir, smb_home_drive, idp, idp_user_id, rename,
+                )
                 certmapdata = convert_certmapdata(certmapdata)
 
                 # Check API specific parameters
@@ -1737,6 +1777,12 @@ def main():
                 else:
                     raise ValueError("No user '%s'" % name)
 
+            elif state == "renamed":
+                if res_find is None:
+                    ansible_module.fail_json(msg="No user '%s'" % name)
+                else:
+                    if rename != name:
+                        commands.append([name, 'user_mod', {"rename": rename}])
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
diff --git a/tests/user/test_user.yml b/tests/user/test_user.yml
index 1300b3b3ef5e4713cb27afcfd0c6c4507c49e47a..48e930dc6feaad0fa4c280d1ad5065e34637e5a9 100644
--- a/tests/user/test_user.yml
+++ b/tests/user/test_user.yml
@@ -9,7 +9,7 @@
     ipauser:
       ipaadmin_password: SomeADMINpassword
       ipaapi_context: "{{ ipa_context | default(omit) }}"
-      name: manager1,manager2,manager3,pinky,pinky2,igagarin
+      name: manager1,manager2,manager3,pinky,pinky2,igagarin,reddy
       state: absent
 
   - name: User manager1 present
@@ -352,6 +352,46 @@
     register: result
     failed_when: result.changed or result.failed
 
+  - name: Rename user pinky to reddy
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      name: pinky
+      rename: reddy
+      state: renamed
+    register: result
+    failed_when: not result.changed or result.failed
+
+  - name: Rename user pinky to reddy, again
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      name: pinky
+      rename: reddy
+      state: renamed
+    register: result
+    failed_when: not result.failed or "No user 'pinky'" not in result.msg
+
+  - name: Rename user reddy to reddy
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      name: reddy
+      rename: reddy
+      state: renamed
+    register: result
+    failed_when: result.changed or result.failed
+
+  - name: Rename user reddy back to pinky
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      ipaapi_context: "{{ ipa_context | default(omit) }}"
+      name: reddy
+      rename: pinky
+      state: renamed
+    register: result
+    failed_when: not result.changed or result.failed
+
   - name: User pinky absent and preserved for future exclusion.
     ipauser:
       ipaadmin_password: SomeADMINpassword
diff --git a/tests/user/test_users.yml b/tests/user/test_users.yml
index 7c0d11e26181ea9d425afb47a056b3dc3aa52b22..651e06d07b6f3f292c115f13eb276a8a88004382 100644
--- a/tests/user/test_users.yml
+++ b/tests/user/test_users.yml
@@ -5,6 +5,12 @@
   gather_facts: false
 
   tasks:
+  - name: Remove test users
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      name: manager1,manager2,manager3,pinky,pinky2,mod1,mod2
+      state: absent
+
   - name: Remove test users
     ipauser:
       ipaadmin_password: SomeADMINpassword
@@ -48,7 +54,7 @@
     register: result
     failed_when: not result.changed or result.failed
 
-  - name: Users user1..10 present
+  - name: Users user1..10 present, again
     ipauser:
       ipaadmin_password: SomeADMINpassword
       users:
@@ -85,6 +91,42 @@
     register: result
     failed_when: result.changed or result.failed
 
+  - name: Rename users user1 and user2 to mod1 and mod1
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      users:
+        - name: user1
+          rename: mod1
+        - name: user2
+          rename: mod2
+      state: renamed
+    register: result
+    failed_when: not result.changed or result.failed
+
+  - name: Rename users mod1 and mod2 to the same name
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      users:
+        - name: mod1
+          rename: mod1
+        - name: mod2
+          rename: mod2
+      state: renamed
+    register: result
+    failed_when: result.changed or result.failed
+
+  - name: Rename users mod1 and mod2 back to user1 and user2
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      users:
+        - name: mod1
+          rename: user1
+        - name: mod2
+          rename: user2
+      state: renamed
+    register: result
+    failed_when: not result.changed or result.failed
+
   # failed_when: not result.failed has been added as this test needs to
   # fail because two users with the same name should be added in the same
   # task.