diff --git a/.gitlab/README.md.template b/.gitlab/README.md.template
index 53feabfa637d99881edb8860458d0ab3dbab0151..523109be86f2e01a9b23b326eba7f20846026a44 100644
--- a/.gitlab/README.md.template
+++ b/.gitlab/README.md.template
@@ -125,9 +125,10 @@ The base image OS can be specified with the `base_os` input.
 ### GitLab-managed Terraform state backend
 
 This component - by leveraging the [`gitlab-tofu`](src/gitlab-tofu.sh) CLI internally -
-automatically configures the
+can automatically define and configure the
 [GitLab-managed Terraform state backend](https://docs.gitlab.com/ee/user/infrastructure/iac/terraform_state.html).
-The only thing required is that the Terraform configuration must specify an empty `http` backend block, like this:
+
+By default the HTTP backend must be defined manually using the following HCL:
 
 ```hcl
 terraform {
@@ -135,8 +136,9 @@ terraform {
 }
 ```
 
-We recommend having a dedicated `backend.tf` file inside your `root_dir`
-with the aforementioned block.
+However, you may simply enable the `auto_define_backend` so that the component takes care of this step.
+
+**Note**: in future versions of this component we may enable `auto_define_backend` by default.
 
 ### State and Plan Encryption
 
diff --git a/Dockerfile.alpine b/Dockerfile.alpine
index 4cc96326857f59914b8ed4b566a63eacda72638d..cf9a8965716370a92b08994beea5c73ce954034c 100644
--- a/Dockerfile.alpine
+++ b/Dockerfile.alpine
@@ -1,4 +1,12 @@
-ARG BASE_IMAGE=alpine:3.21.3
+ARG BASE_IMAGE=docker.io/library/alpine:3.21.3
+
+FROM docker.io/library/golang:1.24-alpine AS builder
+
+WORKDIR /app
+# Copy the Go source code
+COPY src/auto-define-backend /app/
+# Build the Go binary with static linking
+RUN go build -o gitlab-tofu-auto-define-backend .
 
 FROM $BASE_IMAGE
 
@@ -27,6 +35,7 @@ RUN curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opent
 WORKDIR /
 
 COPY --chmod=755 src/gitlab-tofu.sh /usr/bin/gitlab-tofu
+COPY --from=builder --chmod=755 /app/gitlab-tofu-auto-define-backend /usr/bin/
 
 # Override ENTRYPOINT
 ENTRYPOINT []
diff --git a/Dockerfile.debian b/Dockerfile.debian
index 7d93a6c4a0c297ac5facb6d0a4ef93580fd5fa60..e578b7519b33da8de83352755307f63df976351d 100644
--- a/Dockerfile.debian
+++ b/Dockerfile.debian
@@ -1,4 +1,12 @@
-ARG BASE_IMAGE=debian:12.9-slim
+ARG BASE_IMAGE=docker.io/library/debian:12.9-slim
+
+FROM docker.io/library/golang:1.24-bookworm AS builder
+
+WORKDIR /app
+# Copy the Go source code
+COPY src/auto-define-backend /app/
+# Build the Go binary with static linking
+RUN go build -o gitlab-tofu-auto-define-backend .
 
 FROM $BASE_IMAGE
 
@@ -42,6 +50,7 @@ RUN curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opent
 WORKDIR /
 
 COPY --chmod=755 src/gitlab-tofu.sh /usr/bin/gitlab-tofu
+COPY --from=builder --chmod=755 /app/gitlab-tofu-auto-define-backend /usr/bin/
 
 # Override ENTRYPOINT
 ENTRYPOINT []
diff --git a/README.md b/README.md
index 88d55c01c420caee75d7b77d8b369d36ad4cadda..82e05faff9fea8038dc36a938fe52eb3d74da7c4 100644
--- a/README.md
+++ b/README.md
@@ -127,9 +127,10 @@ The base image OS can be specified with the `base_os` input.
 ### GitLab-managed Terraform state backend
 
 This component - by leveraging the [`gitlab-tofu`](src/gitlab-tofu.sh) CLI internally -
-automatically configures the
+can automatically define and configure the
 [GitLab-managed Terraform state backend](https://docs.gitlab.com/ee/user/infrastructure/iac/terraform_state.html).
-The only thing required is that the Terraform configuration must specify an empty `http` backend block, like this:
+
+By default the HTTP backend must be defined manually using the following HCL:
 
 ```hcl
 terraform {
@@ -137,8 +138,9 @@ terraform {
 }
 ```
 
-We recommend having a dedicated `backend.tf` file inside your `root_dir`
-with the aforementioned block.
+However, you may simply enable the `auto_define_backend` so that the component takes care of this step.
+
+**Note**: in future versions of this component we may enable `auto_define_backend` by default.
 
 ### State and Plan Encryption
 
@@ -315,7 +317,8 @@ The following environment variables are respected by the `gitlab-tofu` script:
 - `GITLAB_TOFU_AUTO_ENCRYPTION`: if set to true, enables auto state and plan encryption. Defaults to `false`.
 - `GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE`: the passphrase to use for state and plan encryption. Required if `GITLAB_TOFU_AUTO_ENCRYPTION` is true.
 - `GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED_ENABLED`: if set to true, enables a fallback for state and plan encryption to migrate unencrypted plans and states to encrypted ones. Defaults to `false`.
-- `GITLAB_TOFU_ALLOW_DEVELOPER_ROLE: Users with the Developer role are not able to lock the state. Thus a regular `tofu plan` fails. When set to `true` a `-lock=false` is passed to plan.
+- `GITLAB_TOFU_ALLOW_DEVELOPER_ROLE`: Users with the Developer role are not able to lock the state. Thus a regular `tofu plan` fails. When set to `true` a `-lock=false` is passed to plan.
+- `GITLAB_TOFU_AUTO_DEFINE_BACKEND`: if set to true, automatically creates a file with a HTTP backend configuration block.
 
 #### Respected OpenTofu Environment Variables
 
diff --git a/src/auto-define-backend/go.mod b/src/auto-define-backend/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..4b42fce8dd80d1f1919cfbacdbca8aab2be2ebf0
--- /dev/null
+++ b/src/auto-define-backend/go.mod
@@ -0,0 +1,17 @@
+module gitlab.com/components/opentofu/src/discover-http-backend
+
+go 1.24
+
+require github.com/hashicorp/hcl/v2 v2.23.0
+
+require (
+	github.com/agext/levenshtein v1.2.1 // indirect
+	github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
+	github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
+	github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
+	github.com/zclconf/go-cty v1.13.0 // indirect
+	golang.org/x/mod v0.8.0 // indirect
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.11.0 // indirect
+	golang.org/x/tools v0.6.0 // indirect
+)
diff --git a/src/auto-define-backend/go.sum b/src/auto-define-backend/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..0f49a8988a9b6f8c002159897c652004385587a7
--- /dev/null
+++ b/src/auto-define-backend/go.sum
@@ -0,0 +1,30 @@
+github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
+github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
+github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
+github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
+github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
+github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
+github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
+github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
+github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
+github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0=
+github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
+github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
+github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
diff --git a/src/auto-define-backend/main.go b/src/auto-define-backend/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..29722106e814306f20b24afb6d4c1e45439d2d50
--- /dev/null
+++ b/src/auto-define-backend/main.go
@@ -0,0 +1,134 @@
+package main
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/hashicorp/hcl/v2"
+	"github.com/hashicorp/hcl/v2/hclsyntax"
+)
+
+var ErrManuallyConfigured = errors.New("HTTP backend is manually configured")
+var ErrDifferentManuallyConfigured = errors.New("Backend of different type is manually configured")
+
+func isHTTPBackendConfigured(root *os.Root) (bool, error) {
+	err := fs.WalkDir(root.FS(), ".", func(path string, d fs.DirEntry, err error) error {
+		if path != "." && d.IsDir() {
+			return fs.SkipDir
+		}
+
+		name := filepath.Base(path)
+		ext := filepath.Ext(name)
+		if ext == "" || strings.HasPrefix(name, ".") || strings.HasSuffix(name, "~") || (strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) {
+			return nil
+		}
+
+		if ext == ".tftest.hcl" || ext == ".tftest.json" || ext == ".tofutest.hcl" || ext == ".tofutest.json" {
+			return nil
+		}
+
+		// FIXME: should actually prefer .tofu if .tf does exist, too.
+		if ext != ".tf" && ext != ".tofu" {
+			return nil
+		}
+
+		file, err := root.Open(path)
+		if err != nil {
+			return err
+		}
+
+		data, err := io.ReadAll(file)
+		if err != nil {
+			return err
+		}
+
+		hclFile, diags := hclsyntax.ParseConfig(data, path, hcl.InitialPos)
+		if diags.HasErrors() {
+			return diags
+		}
+
+		root, _, diags := hclFile.Body.PartialContent(&hcl.BodySchema{Blocks: []hcl.BlockHeaderSchema{{Type: "terraform"}}})
+		if diags.HasErrors() {
+			return diags
+		}
+
+		for _, block := range root.Blocks {
+			content, _, diags := block.Body.PartialContent(&hcl.BodySchema{Blocks: []hcl.BlockHeaderSchema{{Type: "backend", LabelNames: []string{"type"}}}})
+			if diags.HasErrors() {
+				return diags
+			}
+			for _, cb := range content.Blocks {
+				if cb.Type == "backend" && len(cb.Labels) == 1 {
+					if cb.Labels[0] == "http" {
+						return ErrManuallyConfigured
+					} else {
+						return ErrDifferentManuallyConfigured
+					}
+				}
+			}
+		}
+		return nil
+	})
+
+	if err != nil {
+		switch err {
+		case ErrManuallyConfigured:
+			return true, nil
+		case ErrDifferentManuallyConfigured:
+			return true, err
+		default:
+			return false, err
+		}
+	}
+
+	return false, nil
+}
+
+func main() {
+	if len(os.Args) < 2 {
+		fmt.Printf("gitlab-tofu: no root directory for backend discovery provided as first argument\n")
+		os.Exit(1)
+	}
+	root, err := os.OpenRoot(os.Args[1])
+	if err != nil {
+		fmt.Printf("gitlab-tofu: unable to open root directory for backend discovery: %s\n", err)
+		os.Exit(1)
+	}
+
+	configured, err := isHTTPBackendConfigured(root)
+	if err != nil {
+		fmt.Printf("gitlab-tofu: error during backend discovery: %s\n", err)
+		os.Exit(1)
+	}
+
+	if configured {
+		fmt.Println("gitlab-tofu: HTTP backend manually configured, doing nothing")
+		os.Exit(0)
+	}
+
+	file, err := root.Create("__gitlab-opentofu-backend.tf")
+	if err != nil {
+		fmt.Printf("gitlab-tofu: failed to create __gitlab-opentofu-backend.tf to automatically define HTTP backend: %s\n", err)
+		os.Exit(1)
+	}
+
+	_, err = file.WriteString(`terraform {
+  backend "http" {}
+}`)
+	if err != nil {
+		fmt.Printf("gitlab-tofu: failed to write __gitlab-opentofu-backend.tf to automatically define HTTP backend: %s\n", err)
+		os.Exit(1)
+	}
+	err = file.Close()
+	if err != nil {
+		fmt.Printf("gitlab-tofu: failed to close __gitlab-opentofu-backend.tf to automatically define HTTP backend: %s\n", err)
+		os.Exit(1)
+	}
+
+	fmt.Printf("gitlab-tofu: automatically defining the HTTP backend in __gitlab-opentofu-backend.tf")
+}
diff --git a/src/gitlab-tofu.sh b/src/gitlab-tofu.sh
index e5ecfc1505eaddb0480a84eaeeb77f61d24dae45..3fb85a90c15cbc58fa9427df207f4b3b8a4c239f 100644
--- a/src/gitlab-tofu.sh
+++ b/src/gitlab-tofu.sh
@@ -30,7 +30,8 @@
 # - `GITLAB_TOFU_AUTO_ENCRYPTION`: if set to true, enables auto state and plan encryption. Defaults to `false`.
 # - `GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE`: the passphrase to use for state and plan encryption. Required if `GITLAB_TOFU_AUTO_ENCRYPTION` is true.
 # - `GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED_ENABLED`: if set to true, enables a fallback for state and plan encryption to migrate unencrypted plans and states to encrypted ones. Defaults to `false`.
-# - `GITLAB_TOFU_ALLOW_DEVELOPER_ROLE: Users with the Developer role are not able to lock the state. Thus a regular `tofu plan` fails. When set to `true` a `-lock=false` is passed to plan.
+# - `GITLAB_TOFU_ALLOW_DEVELOPER_ROLE`: Users with the Developer role are not able to lock the state. Thus a regular `tofu plan` fails. When set to `true` a `-lock=false` is passed to plan.
+# - `GITLAB_TOFU_AUTO_DEFINE_BACKEND`: if set to true, automatically creates a file with a HTTP backend configuration block.
 #
 # #### Respected OpenTofu Environment Variables
 #
@@ -163,6 +164,7 @@ fi
 # ============================
 
 # Backend related variables
+auto_define_backend=${GITLAB_TOFU_AUTO_DEFINE_BACKEND:-false}
 backend_username="gitlab-ci-token"
 backend_password="${CI_JOB_TOKEN}"
 backend_state_name="$(jq -rn --arg x "${GITLAB_TOFU_STATE_NAME:-default}" '$x|@uri')"
@@ -177,6 +179,8 @@ if [ -n "${GITLAB_TOFU_ROOT_DIR}" ]; then
 
   default_tf_plan_cache="${abs_tf_root}/${base_plan_name}.cache"
   default_tf_plan_json="${abs_tf_root}/${base_plan_name}.json"
+else
+  abs_tf_root=$(realpath "${CI_PROJECT_DIR}")
 fi
 
 # Init related variables
@@ -227,6 +231,16 @@ fi
 # Helper functions
 # ================
 
+# define_http_backend defines the HTTP backend in a file called __gitlab-opentofu-backend.tf if no backend can be found.
+# The backend configuration is attempted to be found with a simple grep.
+define_http_backend() {
+  if ! $auto_define_backend; then
+    return
+  fi
+
+  gitlab-tofu-auto-define-backend "${abs_tf_root}"
+}
+
 # configure_variables_for_tofu sets and exports all relevant variables for subsequent `tofu` command invocations.
 configure_variables_for_tofu() {
   # Use terraform automation mode (will remove some verbose unneeded messages)
@@ -352,6 +366,7 @@ EOF
 }
 
 # We always want to configure the tofu variables, even in source-mode.
+define_http_backend
 configure_variables_for_tofu
 configure_encryption_for_tofu
 
diff --git a/templates/apply.yml b/templates/apply.yml
index 9ee6039a4643d35a65806a0ed4e9f9ad0bbba88e..3b98e55eeb8db936292b6a73e0ac272c2fd6b585 100644
--- a/templates/apply.yml
+++ b/templates/apply.yml
@@ -98,6 +98,10 @@ spec:
       default: false
       type: boolean
       description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
+    auto_define_backend:
+      default: false
+      type: boolean
+      description: 'Whether to automatically define the HTTP backend configuration block.'
 
 ---
 
@@ -124,6 +128,7 @@ spec:
     GITLAB_TOFU_AUTO_ENCRYPTION: '$[[ inputs.auto_encryption ]]'
     GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
     GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
+    GITLAB_TOFU_AUTO_DEFINE_BACKEND: '$[[ inputs.auto_define_backend ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/templates/destroy.yml b/templates/destroy.yml
index 90b1e55d43a753681b82c43d8df0636a6621bee5..fa350f8b0f2fb24c07358cd81c3cce32e1bee4da 100644
--- a/templates/destroy.yml
+++ b/templates/destroy.yml
@@ -102,6 +102,10 @@ spec:
       default: false
       type: boolean
       description: 'Whether to automatically delete the Terraform state. This only makes sense when using the GitLab-managed state backend. It is equivalent to running the delete-state job.'
+    auto_define_backend:
+      default: false
+      type: boolean
+      description: 'Whether to automatically define the HTTP backend configuration block.'
 
 ---
 
@@ -128,6 +132,7 @@ spec:
     GITLAB_TOFU_AUTO_ENCRYPTION: '$[[ inputs.auto_encryption ]]'
     GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
     GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
+    GITLAB_TOFU_AUTO_DEFINE_BACKEND: '$[[ inputs.auto_define_backend ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/templates/full-pipeline.yml b/templates/full-pipeline.yml
index f4f22f14c7f4b0a1f17d87a7d9d4499563ccf52f..290e81bb2925a828d76275a7c2aa2b78ef390dfb 100644
--- a/templates/full-pipeline.yml
+++ b/templates/full-pipeline.yml
@@ -198,6 +198,10 @@ spec:
       default: false
       type: boolean
       description: 'Users with the Developer role are not able to lock the state. Thus a regular `tofu plan` fails. When set to `true` a `-lock=false` is passed to plan.'
+    auto_define_backend:
+      default: false
+      type: boolean
+      description: 'Whether to automatically define the HTTP backend configuration block.'
 
 ---
 
@@ -236,6 +240,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/test.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "true"'
@@ -259,6 +264,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/plan.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -282,6 +288,7 @@ include:
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
       allow_developer_role: $[[ inputs.allow_developer_role_to_plan ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/apply.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -302,6 +309,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/destroy.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -321,6 +329,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/delete-state.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -408,6 +417,7 @@ stages:
           auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
           auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
           allow_developer_role_to_plan: $[[ inputs.allow_developer_role_to_plan ]]
+          auto_define_backend: $[[ inputs.auto_define_backend ]]
           trigger_in_child_pipeline: false
     forward:
       yaml_variables: true
diff --git a/templates/graph.yml b/templates/graph.yml
index 02da5178c8b6ef647298a4e0261abe490b0c0562..c78cde0ae86b353bdd6d2a1719f1dd817cc31e6c 100644
--- a/templates/graph.yml
+++ b/templates/graph.yml
@@ -96,6 +96,10 @@ spec:
       default: false
       type: boolean
       description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
+    auto_define_backend:
+      default: false
+      type: boolean
+      description: 'Whether to automatically define the HTTP backend configuration block.'
 
 ---
 
@@ -116,6 +120,7 @@ spec:
     GITLAB_TOFU_AUTO_ENCRYPTION: '$[[ inputs.auto_encryption ]]'
     GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
     GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
+    GITLAB_TOFU_AUTO_DEFINE_BACKEND: '$[[ inputs.auto_define_backend ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/templates/job-templates.yml b/templates/job-templates.yml
index 387f02f11691b2372a24189596f314543df3deae..eaa2d306161fccc811d3318729eef823ccab3066 100644
--- a/templates/job-templates.yml
+++ b/templates/job-templates.yml
@@ -103,6 +103,10 @@ spec:
       default: false
       type: boolean
       description: 'Users with the Developer role are not able to lock the state. Thus a regular `tofu plan` fails. When set to `true` a `-lock=false` is passed to plan.'
+    auto_define_backend:
+      default: false
+      type: boolean
+      description: 'Whether to automatically define the HTTP backend configuration block.'
 
 ---
 
@@ -134,6 +138,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/graph.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]graph'
@@ -148,6 +153,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/test.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]test'
@@ -164,6 +170,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/plan.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]plan'
@@ -183,6 +190,7 @@ include:
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
       allow_developer_role: $[[ inputs.allow_developer_role_to_plan ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/apply.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]apply'
@@ -200,6 +208,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/destroy.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]destroy'
@@ -217,6 +226,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/delete-state.yml'
     inputs:
       as: '$[[ inputs.job_name_prefix ]]delete-state'
diff --git a/templates/plan.yml b/templates/plan.yml
index 752c37c99f7d1cebf2e4827a54f31c2e19c81bb2..73b4a5118416234e0b2bfe08d384d34baeda9690 100644
--- a/templates/plan.yml
+++ b/templates/plan.yml
@@ -109,6 +109,10 @@ spec:
       default: false
       type: boolean
       description: 'Users with the Developer role are not able to lock the state. Thus a regular `tofu plan` fails. When set to `true` a `-lock=false` is passed to plan.'
+    auto_define_backend:
+      default: false
+      type: boolean
+      description: 'Whether to automatically define the HTTP backend configuration block.'
 
 ---
 
@@ -175,6 +179,7 @@ spec:
     GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
     GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
     GITLAB_TOFU_ALLOW_DEVELOPER_ROLE: '$[[ inputs.allow_developer_role ]]'
+    GITLAB_TOFU_AUTO_DEFINE_BACKEND: '$[[ inputs.auto_define_backend ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/templates/test.yml b/templates/test.yml
index 79b2b0b717b1061ea8378be15023478ab0dde33d..a76e3293c24b73d6d05dc913cc4e1951152aa795 100644
--- a/templates/test.yml
+++ b/templates/test.yml
@@ -98,6 +98,10 @@ spec:
       default: false
       type: boolean
       description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
+    auto_define_backend:
+      default: false
+      type: boolean
+      description: 'Whether to automatically define the HTTP backend configuration block.'
 
 ---
 
@@ -119,6 +123,7 @@ spec:
     GITLAB_TOFU_AUTO_ENCRYPTION: '$[[ inputs.auto_encryption ]]'
     GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
     GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
+    GITLAB_TOFU_AUTO_DEFINE_BACKEND: '$[[ inputs.auto_define_backend ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/templates/validate-plan-apply.yml b/templates/validate-plan-apply.yml
index 6924516558cabbc5ab96f5c8ca1c04ae4acd4f12..f1c50051c4fbb540644bcf1cbab08ec7602a38f6 100644
--- a/templates/validate-plan-apply.yml
+++ b/templates/validate-plan-apply.yml
@@ -162,6 +162,10 @@ spec:
       default: false
       type: boolean
       description: 'Users with the Developer role are not able to lock the state. Thus a regular `tofu plan` fails. When set to `true` a `-lock=false` is passed to plan.'
+    auto_define_backend:
+      default: false
+      type: boolean
+      description: 'Whether to automatically define the HTTP backend configuration block.'
 
 ---
 
@@ -201,6 +205,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/plan.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -225,6 +230,7 @@ include:
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
       allow_developer_role: $[[ inputs.allow_developer_role_to_plan ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/apply.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -246,6 +252,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
 
 
 # NOTE: the following configuration is only used if `trigger_in_child_pipeline` is enabled.
@@ -303,6 +310,7 @@ stages:
           auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
           auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
           allow_developer_role_to_plan: $[[ inputs.allow_developer_role_to_plan ]]
+          auto_define_backend: $[[ inputs.auto_define_backend ]]
           trigger_in_child_pipeline: false
     forward:
       yaml_variables: true
diff --git a/templates/validate-plan-destroy.yml b/templates/validate-plan-destroy.yml
index c6a54b711f241431874d9ec7cff9bda8bcd69897..1d303fd6eaef9113cd84dadbc85c78b7b7614f63 100644
--- a/templates/validate-plan-destroy.yml
+++ b/templates/validate-plan-destroy.yml
@@ -168,6 +168,10 @@ spec:
       default: false
       type: boolean
       description: 'Users with the Developer role are not able to lock the state. Thus a regular `tofu plan` fails. When set to `true` a `-lock=false` is passed to plan.'
+    auto_define_backend:
+      default: false
+      type: boolean
+      description: 'Whether to automatically define the HTTP backend configuration block.'
 
 ---
 
@@ -207,6 +211,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/plan.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -232,6 +237,7 @@ include:
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
       allow_developer_role: $[[ inputs.allow_developer_role_to_plan ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/destroy.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -254,6 +260,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/delete-state.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -336,6 +343,7 @@ stages:
           auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
           auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
           allow_developer_role_to_plan: $[[ inputs.allow_developer_role_to_plan ]]
+          auto_define_backend: $[[ inputs.auto_define_backend ]]
           trigger_in_child_pipeline: false
     forward:
       yaml_variables: true
diff --git a/templates/validate-plan.yml b/templates/validate-plan.yml
index 96d199e6fb0db2007e827cd7cbc99c7aa9b4320f..ed976b6f333474f60bed15a95a26b4bcec0b4559 100644
--- a/templates/validate-plan.yml
+++ b/templates/validate-plan.yml
@@ -146,6 +146,10 @@ spec:
       default: false
       type: boolean
       description: 'Users with the Developer role are not able to lock the state. Thus a regular `tofu plan` fails. When set to `true` a `-lock=false` is passed to plan.'
+    auto_define_backend:
+      default: false
+      type: boolean
+      description: 'Whether to automatically define the HTTP backend configuration block.'
 
 ---
 
@@ -185,6 +189,7 @@ include:
       auto_encryption: $[[ inputs.auto_encryption ]]
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
   - local: '/templates/plan.yml'
     rules:
       - if: '"$[[ inputs.trigger_in_child_pipeline ]]" == "false"'
@@ -209,6 +214,7 @@ include:
       auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
       auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
       allow_developer_role: $[[ inputs.allow_developer_role_to_plan ]]
+      auto_define_backend: $[[ inputs.auto_define_backend ]]
 
 
 # NOTE: the following configuration is only used if `trigger_in_child_pipeline` is enabled.
@@ -263,6 +269,7 @@ stages:
           auto_encryption_passphrase: $[[ inputs.auto_encryption_passphrase ]]
           auto_encryption_enable_migration_from_unencrypted: $[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]
           allow_developer_role_to_plan: $[[ inputs.allow_developer_role_to_plan ]]
+          auto_define_backend: $[[ inputs.auto_define_backend ]]
           trigger_in_child_pipeline: false
     forward:
       yaml_variables: true
diff --git a/templates/validate.yml b/templates/validate.yml
index e4dcb8e17a20118fb06f8618b3fc01bb438f8ae0..14154a195b7b0b668e41a8ad6861c94f22c8d6e1 100644
--- a/templates/validate.yml
+++ b/templates/validate.yml
@@ -95,6 +95,10 @@ spec:
       default: false
       type: boolean
       description: 'Whether to setup automatic state and plan encryption for currently unencrypted state. This is only temporarily useful when migrating from an unencrypted state.'
+    auto_define_backend:
+      default: false
+      type: boolean
+      description: 'Whether to automatically define the HTTP backend configuration block.'
 
 ---
 
@@ -116,6 +120,7 @@ spec:
     GITLAB_TOFU_AUTO_ENCRYPTION: '$[[ inputs.auto_encryption ]]'
     GITLAB_TOFU_AUTO_ENCRYPTION_PASSPHRASE: '$[[ inputs.auto_encryption_passphrase ]]'
     GITLAB_TOFU_AUTO_ENCRYPTION_ENABLE_MIGRATION_FROM_UNENCRYPTED: '$[[ inputs.auto_encryption_enable_migration_from_unencrypted ]]'
+    GITLAB_TOFU_AUTO_DEFINE_BACKEND: '$[[ inputs.auto_define_backend ]]'
   image:
     name: '$[[ inputs.image_registry_base ]]/$[[ inputs.image_name ]]:$[[ inputs.version ]]-opentofu$[[ inputs.opentofu_version ]]-$[[ inputs.base_os ]]$[[ inputs.image_digest ]]'
   script:
diff --git a/tests/iac-auto-define-backend/main.tf b/tests/iac-auto-define-backend/main.tf
new file mode 100644
index 0000000000000000000000000000000000000000..6f610dcb023f602dc4d7920121c713d25e627fc7
--- /dev/null
+++ b/tests/iac-auto-define-backend/main.tf
@@ -0,0 +1,19 @@
+resource "local_file" "foo" {
+  content  = "foo!"
+  filename = "${path.module}/foo.bar"
+}
+
+locals {
+  ts = plantimestamp()
+}
+
+// NOTE: always force a change.
+resource "null_resource" "this" {
+  triggers = {
+    timestamp = local.ts
+  }
+}
+
+output "this_always_changes" {
+  value = local.ts
+}
diff --git a/tests/iac-auto-define-backend/tests/main.tftest.hcl b/tests/iac-auto-define-backend/tests/main.tftest.hcl
new file mode 100644
index 0000000000000000000000000000000000000000..fedf96e4c458eee084740b05980a52ffe64362a1
--- /dev/null
+++ b/tests/iac-auto-define-backend/tests/main.tftest.hcl
@@ -0,0 +1,6 @@
+run "test" {
+  assert {
+    condition     = file(local_file.foo.filename) == "foo!"
+    error_message = "Incorrect content in ${local_file.foo.filename}"
+  }
+}
diff --git a/tests/integration-tests/AutoDefineBackend.gitlab-ci.yml b/tests/integration-tests/AutoDefineBackend.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4a64e52cafecf9fc001f5a2dca1e185f76f875ba
--- /dev/null
+++ b/tests/integration-tests/AutoDefineBackend.gitlab-ci.yml
@@ -0,0 +1,20 @@
+include:
+  - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/full-pipeline@$CI_COMMIT_SHA
+    inputs:
+      image_registry_base: $GITLAB_OPENTOFU_IMAGE_BASE
+      version: $CI_COMMIT_SHA
+      base_os: $GITLAB_OPENTOFU_BASE_IMAGE_OS
+      opentofu_version: $OPENTOFU_VERSION
+      root_dir: $TEST_GITLAB_TOFU_ROOT_DIR
+      state_name: $TEST_GITLAB_TOFU_STATE_NAME
+      auto_define_backend: true
+      # Required to run everything immediately, instead of manually.
+      fmt_rules: [{when: always}]
+      validate_rules: [{when: always}]
+      test_rules: [{when: always}]
+      plan_rules: [{when: always}]
+      apply_rules: [{when: always}]
+      destroy_rules: [{when: always}]
+      delete_state_rules: [{when: always}]
+
+stages: [validate, test, build, deploy, cleanup]
diff --git a/tests/integration.gitlab-ci.yml b/tests/integration.gitlab-ci.yml
index c9298a573791c160dc6e1af085ba4bf823ea5ef4..f5ed0b8e88b23543c0a6c8f2a335c8457b73ae6d 100644
--- a/tests/integration.gitlab-ci.yml
+++ b/tests/integration.gitlab-ci.yml
@@ -17,6 +17,23 @@ full-pipeline:
           - alpine
           - debian
 
+auto-define-backend:
+  stage: test-integration
+  variables:
+    OPENTOFU_VERSION: $LATEST_OPENTOFU_VERSION
+    TEST_GITLAB_TOFU_STATE_NAME: ci-integration-$CI_JOB_NAME_SLUG-$CI_PIPELINE_IID-$CI_NODE_INDEX
+    TEST_GITLAB_TOFU_ROOT_DIR: tests/iac-auto-define-backend
+  trigger:
+    include: tests/integration-tests/$PIPELINE_NAME.gitlab-ci.yml
+    strategy: depend
+  parallel:
+    matrix:
+      - PIPELINE_NAME:
+          - AutoDefineBackend
+        GITLAB_OPENTOFU_BASE_IMAGE_OS:
+          - alpine
+          - debian
+
 validate-plan-apply:
   stage: test-integration
   variables: