diff --git a/app/models.py b/app/models.py
index ab17530547f38d9239b0b061339c47eff71f14d0..80c80a21a04b0c9207f891d26cf4205e609230e7 100644
--- a/app/models.py
+++ b/app/models.py
@@ -32,6 +32,7 @@ from .plugins import FlaskUserPlugin
 from .validators import (
     ICS_ID_RE,
     HOST_NAME_RE,
+    GROUP_NAME_RE,
     INTERFACE_NAME_RE,
     VLAN_NAME_RE,
     MAC_ADDRESS_RE,
@@ -1169,6 +1170,20 @@ class AnsibleGroup(CreatedMixin, SearchableMixin, db.Model):
         check_parents(self)
         return child
 
+    @validates("name")
+    def validate_name(self, key, string):
+        """Ensure the name matches the required format"""
+        if string is None:
+            return None
+        # Force the string to lowercase
+        lower_string = string.lower()
+        if GROUP_NAME_RE.fullmatch(lower_string) is None:
+            raise ValidationError(f"Group name shall match {GROUP_NAME_RE.pattern}")
+        existing_group_name = AnsibleGroup.query.filter_by(name=lower_string).first()
+        if existing_group_name:
+            raise ValidationError("Group name matches an existing group")
+        return lower_string
+
     @property
     def is_dynamic(self):
         return self.type != AnsibleGroupType.STATIC
diff --git a/app/validators.py b/app/validators.py
index d6b222158c9f84bc168ff920f53ad4978b7c49c5..e084d550b4725adca5c5a85f0ad24f7ed7b456a9 100644
--- a/app/validators.py
+++ b/app/validators.py
@@ -16,6 +16,7 @@ from wtforms import ValidationError, SelectField
 
 ICS_ID_RE = re.compile(r"^[A-Z]{3}[0-9]{3}$")
 HOST_NAME_RE = re.compile(r"^[a-z0-9\-]{2,24}$")
+GROUP_NAME_RE = re.compile(r"^[a-z0-9\-\_]{2,50}$")
 # Interface name needs to be at least 5 characters more than the hostname (Every interface name have to start with the hostname)
 INTERFACE_NAME_RE = re.compile(r"^[a-z0-9\-]{2,29}$")
 VLAN_NAME_RE = re.compile(r"^[A-Za-z0-9\-]{3,25}$")