diff --git a/ee/app/assets/javascripts/security_configuration/dast_scanner_profiles/components/dast_scanner_profile_form.vue b/ee/app/assets/javascripts/security_configuration/dast_scanner_profiles/components/dast_scanner_profile_form.vue
index 1ac14e25bc7c5edad102622d516611622ea6c81b..070a1c92665aa1472adc09a2d2c957cb837be920 100644
--- a/ee/app/assets/javascripts/security_configuration/dast_scanner_profiles/components/dast_scanner_profile_form.vue
+++ b/ee/app/assets/javascripts/security_configuration/dast_scanner_profiles/components/dast_scanner_profile_form.vue
@@ -14,8 +14,9 @@ import {
 import * as Sentry from '@sentry/browser';
 import { isEqual } from 'lodash';
 import { initFormField } from 'ee/security_configuration/utils';
-import { serializeFormObject, isEmptyValue } from '~/lib/utils/forms';
+import { serializeFormObject } from '~/lib/utils/forms';
 import { __, s__ } from '~/locale';
+import validation from '~/vue_shared/directives/validation';
 import { SCAN_TYPE, SCAN_TYPE_OPTIONS } from '../constants';
 import dastScannerProfileCreateMutation from '../graphql/dast_scanner_profile_create.mutation.graphql';
 import dastScannerProfileUpdateMutation from '../graphql/dast_scanner_profile_update.mutation.graphql';
@@ -41,6 +42,9 @@ export default {
     GlFormRadioGroup,
     tooltipIcon,
   },
+  directives: {
+    validation: validation(),
+  },
   props: {
     projectFullPath: {
       type: String,
@@ -68,17 +72,29 @@ export default {
     } = this.profile;
 
     const form = {
-      profileName: initFormField({ value: profileName }),
-      spiderTimeout: initFormField({ value: spiderTimeout }),
-      targetTimeout: initFormField({ value: targetTimeout }),
-      scanType: initFormField({ value: scanType }),
-      useAjaxSpider: initFormField({ value: useAjaxSpider }),
-      showDebugMessages: initFormField({ value: showDebugMessages }),
+      state: false,
+      showValidation: false,
+      fields: {
+        profileName: initFormField({ value: profileName }),
+        spiderTimeout: initFormField({ value: spiderTimeout }),
+        targetTimeout: initFormField({ value: targetTimeout }),
+        scanType: initFormField({ value: scanType, required: false, skipValidation: true }),
+        useAjaxSpider: initFormField({
+          value: useAjaxSpider,
+          required: false,
+          skipValidation: true,
+        }),
+        showDebugMessages: initFormField({
+          value: showDebugMessages,
+          required: false,
+          skipValidation: true,
+        }),
+      },
     };
 
     return {
       form,
-      initialFormValues: serializeFormObject(form),
+      initialFormValues: serializeFormObject(form.fields),
       loading: false,
       showAlert: false,
     };
@@ -130,18 +146,10 @@ export default {
       };
     },
     formTouched() {
-      return !isEqual(serializeFormObject(this.form), this.initialFormValues);
-    },
-    formHasErrors() {
-      return Object.values(this.form).some(({ state }) => state === false);
-    },
-    requiredFieldEmpty() {
-      return Object.values(this.form).some(
-        ({ required, value }) => required && isEmptyValue(value),
-      );
+      return !isEqual(serializeFormObject(this.form.fields), this.initialFormValues);
     },
     isSubmitDisabled() {
-      return this.formHasErrors || this.requiredFieldEmpty || this.isPolicyProfile;
+      return this.isPolicyProfile;
     },
     isPolicyProfile() {
       return Boolean(this.profile?.referencedInSecurityPolicies?.length);
@@ -149,27 +157,13 @@ export default {
   },
 
   methods: {
-    validateTimeout(timeoutObject, range) {
-      const timeout = timeoutObject;
-
-      const hasValue = timeout.value !== '';
-      const isOutOfRange = timeout.value < range.min || timeout.value > range.max;
+    onSubmit() {
+      this.form.showValidation = true;
 
-      if (hasValue && isOutOfRange) {
-        timeout.state = false;
-        timeout.feedback = s__('DastProfiles|Please enter a valid timeout value');
+      if (!this.form.state) {
         return;
       }
-      timeout.state = true;
-      timeout.feedback = null;
-    },
-    validateSpiderTimeout() {
-      this.validateTimeout(this.form.spiderTimeout, this.$options.spiderTimeoutRange);
-    },
-    validateTargetTimeout() {
-      this.validateTimeout(this.form.targetTimeout, this.$options.targetTimeoutRange);
-    },
-    onSubmit() {
+
       this.loading = true;
       this.hideErrors();
 
@@ -177,7 +171,7 @@ export default {
         input: {
           fullPath: this.projectFullPath,
           ...(this.isEdit ? { id: this.profile.id } : {}),
-          ...serializeFormObject(this.form),
+          ...serializeFormObject(this.form.fields),
         },
       };
 
@@ -237,7 +231,7 @@ export default {
 </script>
 
 <template>
-  <gl-form @submit.prevent="onSubmit">
+  <gl-form novalidate @submit.prevent="onSubmit">
     <h2 v-if="showHeader" class="gl-mb-6">{{ i18n.title }}</h2>
 
     <gl-alert
@@ -268,13 +262,19 @@ export default {
     </gl-alert>
 
     <gl-form-group data-testid="dast-scanner-parent-group" :disabled="isPolicyProfile">
-      <gl-form-group :label="s__('DastProfiles|Profile name')">
+      <gl-form-group
+        :label="s__('DastProfiles|Profile name')"
+        :invalid-feedback="form.fields.profileName.feedback"
+      >
         <gl-form-input
-          v-model="form.profileName.value"
-          name="profile_name"
+          v-model="form.fields.profileName.value"
+          v-validation:[form.showValidation]
+          name="profileName"
           class="mw-460"
           data-testid="profile-name-input"
           type="text"
+          required
+          :state="form.fields.profileName.state"
         />
       </gl-form-group>
 
@@ -287,7 +287,7 @@ export default {
         </template>
 
         <gl-form-radio-group
-          v-model="form.scanType.value"
+          v-model="form.fields.scanType.value"
           :options="$options.SCAN_TYPE_OPTIONS"
           data-testid="scan-type-option"
         />
@@ -296,22 +296,24 @@ export default {
       <div class="row">
         <gl-form-group
           class="col-md-6 mb-0"
-          :state="form.spiderTimeout.state"
-          :invalid-feedback="form.spiderTimeout.feedback"
+          :invalid-feedback="form.fields.spiderTimeout.feedback"
+          :state="form.fields.spiderTimeout.state"
         >
           <template #label>
             {{ s__('DastProfiles|Spider timeout') }}
             <tooltip-icon :title="i18n.tooltips.spiderTimeout" />
           </template>
           <gl-form-input-group
-            v-model.number="form.spiderTimeout.value"
-            name="spider_timeout"
+            v-model.number="form.fields.spiderTimeout.value"
+            v-validation:[form.showValidation]
+            name="spiderTimeout"
             class="mw-460"
             data-testid="spider-timeout-input"
             type="number"
             :min="$options.spiderTimeoutRange.min"
             :max="$options.spiderTimeoutRange.max"
-            @input="validateSpiderTimeout"
+            :state="form.fields.spiderTimeout.state"
+            required
           >
             <template #append>
               <gl-input-group-text>{{ __('Minutes') }}</gl-input-group-text>
@@ -324,22 +326,24 @@ export default {
 
         <gl-form-group
           class="col-md-6 mb-0"
-          :state="form.targetTimeout.state"
-          :invalid-feedback="form.targetTimeout.feedback"
+          :invalid-feedback="form.fields.targetTimeout.feedback"
+          :state="form.fields.targetTimeout.state"
         >
           <template #label>
             {{ s__('DastProfiles|Target timeout') }}
             <tooltip-icon :title="i18n.tooltips.targetTimeout" />
           </template>
           <gl-form-input-group
-            v-model.number="form.targetTimeout.value"
-            name="target_timeout"
+            v-model.number="form.fields.targetTimeout.value"
+            v-validation:[form.showValidation]
+            name="targetTimeout"
             class="mw-460"
             data-testid="target-timeout-input"
             type="number"
             :min="$options.targetTimeoutRange.min"
             :max="$options.targetTimeoutRange.max"
-            @input="validateTargetTimeout"
+            :state="form.fields.targetTimeout.state"
+            required
           >
             <template #append>
               <gl-input-group-text>{{ __('Seconds') }}</gl-input-group-text>
@@ -359,7 +363,7 @@ export default {
             {{ s__('DastProfiles|AJAX spider') }}
             <tooltip-icon :title="i18n.tooltips.ajaxSpider" />
           </template>
-          <gl-form-checkbox v-model="form.useAjaxSpider.value">{{
+          <gl-form-checkbox v-model="form.fields.useAjaxSpider.value">{{
             s__('DastProfiles|Turn on AJAX spider')
           }}</gl-form-checkbox>
         </gl-form-group>
@@ -369,7 +373,7 @@ export default {
             {{ s__('DastProfiles|Debug messages') }}
             <tooltip-icon :title="i18n.tooltips.debugMessage" />
           </template>
-          <gl-form-checkbox v-model="form.showDebugMessages.value">{{
+          <gl-form-checkbox v-model="form.fields.showDebugMessages.value">{{
             s__('DastProfiles|Show debug messages')
           }}</gl-form-checkbox>
         </gl-form-group>
diff --git a/ee/changelogs/unreleased/djadmin-scanner-profile-validations.yml b/ee/changelogs/unreleased/djadmin-scanner-profile-validations.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5ac75102d687d19e1eb0a6ccfdc87eaac0dee5e1
--- /dev/null
+++ b/ee/changelogs/unreleased/djadmin-scanner-profile-validations.yml
@@ -0,0 +1,5 @@
+---
+title: Always display submit button for DAST Scanner Profile form
+merge_request: 59243
+author:
+type: changed
diff --git a/ee/spec/features/projects/security/dast_scanner_profiles_spec.rb b/ee/spec/features/projects/security/dast_scanner_profiles_spec.rb
index eb77c4c9258e7c21c9dabcfbbaab0ab9181e4601..d9df23115f629050c97dff865006f8f14f32243b 100644
--- a/ee/spec/features/projects/security/dast_scanner_profiles_spec.rb
+++ b/ee/spec/features/projects/security/dast_scanner_profiles_spec.rb
@@ -50,9 +50,9 @@
   end
 
   def fill_in_profile_form
-    fill_in 'profile_name', with: "hello"
-    fill_in 'spider_timeout', with: "1"
-    fill_in 'target_timeout', with: "2"
+    fill_in 'profileName', with: "hello"
+    fill_in 'spiderTimeout', with: "1"
+    fill_in 'targetTimeout', with: "2"
     click_button 'Save profile'
     wait_for_requests
   end
diff --git a/ee/spec/frontend/security_configuration/dast_scanner_profiles_form/components/dast_scanner_profiles_form_spec.js b/ee/spec/frontend/security_configuration/dast_scanner_profiles_form/components/dast_scanner_profiles_form_spec.js
index f6a9bdd8326b5bbfc10a9f91c667e993587c6634..50295c3f4c10175d8b81b44d99e22c02ea3d2366 100644
--- a/ee/spec/frontend/security_configuration/dast_scanner_profiles_form/components/dast_scanner_profiles_form_spec.js
+++ b/ee/spec/frontend/security_configuration/dast_scanner_profiles_form/components/dast_scanner_profiles_form_spec.js
@@ -49,6 +49,18 @@ describe('DAST Scanner Profile', () => {
   const findPolicyProfileAlert = () => findByTestId('dast-policy-scanner-profile-alert');
   const submitForm = () => findForm().vm.$emit('submit', { preventDefault: () => {} });
 
+  const setFieldValue = async (field, value) => {
+    await field.find('input').setValue(value);
+    field.trigger('blur');
+  };
+
+  const fillAndSubmitForm = async () => {
+    await setFieldValue(findProfileNameInput(), profileName);
+    await setFieldValue(findSpiderTimeoutInput(), spiderTimeout);
+    await setFieldValue(findTargetTimeoutInput(), targetTimeout);
+    await submitForm();
+  };
+
   const componentFactory = (mountFn = shallowMount) => (options) => {
     wrapper = mountFn(
       DastScannerProfileForm,
@@ -94,18 +106,18 @@ describe('DAST Scanner Profile', () => {
       createComponent();
     });
 
-    describe('is disabled if', () => {
+    describe('is enabled even if', () => {
       it('form contains errors', async () => {
         findProfileNameInput().vm.$emit('input', profileName);
         await findSpiderTimeoutInput().vm.$emit('input', '12312');
-        expect(findSubmitButton().props('disabled')).toBe(true);
+        expect(findSubmitButton().props('disabled')).toBe(false);
       });
 
       it('at least one field is empty', async () => {
         findProfileNameInput().vm.$emit('input', '');
         await findSpiderTimeoutInput().vm.$emit('input', spiderTimeout);
         await findTargetTimeoutInput().vm.$emit('input', targetTimeout);
-        expect(findSubmitButton().props('disabled')).toBe(true);
+        expect(findSubmitButton().props('disabled')).toBe(false);
       });
     });
 
@@ -124,21 +136,19 @@ describe('DAST Scanner Profile', () => {
     ${'Spider'} | ${findSpiderTimeoutInput} | ${[-1, 2881]} | ${spiderTimeout}
     ${'Target'} | ${findTargetTimeoutInput} | ${[0, 3601]}  | ${targetTimeout}
   `('$timeoutType Timeout', ({ finder, invalidValues, validValue }) => {
-    const errorMessage = 'Please enter a valid timeout value';
+    const errorMessage = 'Constraints not satisfied';
 
     beforeEach(() => {
       createFullComponent();
     });
 
     it.each(invalidValues)('is marked as invalid provided an invalid value', async (value) => {
-      await finder().find('input').setValue(value);
-
+      await setFieldValue(finder().find('input'), value);
       expect(wrapper.text()).toContain(errorMessage);
     });
 
     it('is marked as valid provided a valid value', async () => {
-      await finder().find('input').setValue(validValue);
-
+      await setFieldValue(finder().find('input'), validValue);
       expect(wrapper.text()).not.toContain(errorMessage);
     });
 
@@ -175,14 +185,14 @@ describe('DAST Scanner Profile', () => {
       const createdProfileId = 30203;
 
       describe('on success', () => {
-        beforeEach(() => {
+        beforeEach(async () => {
           jest
             .spyOn(wrapper.vm.$apollo, 'mutate')
             .mockResolvedValue({ data: { [mutationKind]: { id: createdProfileId } } });
-          findProfileNameInput().vm.$emit('input', profileName);
-          findSpiderTimeoutInput().vm.$emit('input', spiderTimeout);
-          findTargetTimeoutInput().vm.$emit('input', targetTimeout);
-          submitForm();
+          await findProfileNameInput().vm.$emit('input', profileName);
+          await findSpiderTimeoutInput().vm.$emit('input', spiderTimeout);
+          await findTargetTimeoutInput().vm.$emit('input', targetTimeout);
+          await submitForm();
         });
 
         it('sets loading state', () => {
@@ -219,19 +229,17 @@ describe('DAST Scanner Profile', () => {
       });
 
       describe('on top-level error', () => {
-        beforeEach(() => {
-          createComponent();
+        beforeEach(async () => {
+          createFullComponent();
           jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue();
-          const input = findTargetTimeoutInput();
-          input.vm.$emit('input', targetTimeout);
-          submitForm();
+          await fillAndSubmitForm();
         });
 
         it('resets loading state', () => {
           expect(findSubmitButton().props('loading')).toBe(false);
         });
 
-        it('shows an error alert', () => {
+        it('shows an error alert', async () => {
           expect(findAlert().exists()).toBe(true);
         });
       });
@@ -239,13 +247,14 @@ describe('DAST Scanner Profile', () => {
       describe('on errors as data', () => {
         const errors = ['Name is already taken', 'Value should be Int', 'error#3'];
 
-        beforeEach(() => {
+        beforeEach(async () => {
           jest
             .spyOn(wrapper.vm.$apollo, 'mutate')
             .mockResolvedValue({ data: { [mutationKind]: { errors } } });
-          const input = findSpiderTimeoutInput();
-          input.vm.$emit('input', spiderTimeout);
-          submitForm();
+          await findProfileNameInput().vm.$emit('input', profileName);
+          await findSpiderTimeoutInput().vm.$emit('input', spiderTimeout);
+          await findTargetTimeoutInput().vm.$emit('input', targetTimeout);
+          await submitForm();
         });
 
         it('resets loading state', () => {
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 557cf5525b6eb5ef7c808b53f8ac9e07247db72c..9b638aa1a6370e73eecc5e412dd14462f3f81756 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -10199,9 +10199,6 @@ msgstr ""
 msgid "DastProfiles|Password form field"
 msgstr ""
 
-msgid "DastProfiles|Please enter a valid timeout value"
-msgstr ""
-
 msgid "DastProfiles|Profile name"
 msgstr ""