diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index eb3e4ae..04130b0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,11 +4,25 @@ on:
   push:
     branches: [master]
 
+env: 
+  RESTIC_VERSION: "0.17.1"
+
 jobs:
   test:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v3
+
+      - name: Install restic@${{ env.RESTIC_VERSION }}
+        run: |
+          mkdir -p tools/restic
+          curl --fail --location --silent --show-error --output tools/restic/restic.bz2 \
+            "https://github.com/restic/restic/releases/download/v${RESTIC_VERSION}/restic_${RESTIC_VERSION}_linux_amd64.bz2"
+          bzip2 -d tools/restic/restic.bz2
+          chmod +x tools/restic/restic
+          echo "$GITHUB_WORKSPACE/tools/restic" >> "$GITHUB_PATH"
+      - run: restic version
+
       - uses: actions/setup-go@v3
         with:
           go-version: '^1.21'
diff --git a/cmd/backup_test.go b/cmd/backup_test.go
new file mode 100644
index 0000000..2b1287f
--- /dev/null
+++ b/cmd/backup_test.go
@@ -0,0 +1,73 @@
+package cmd
+
+import (
+	"os"
+	"path"
+	"testing"
+
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/assert"
+	"gopkg.in/yaml.v3"
+)
+
+func runCmd(t *testing.T, args ...string) error {
+	t.Helper()
+
+	viper.Reset()
+	rootCmd.SetArgs(args)
+
+	err := rootCmd.Execute()
+	return err
+}
+
+func TestBackupCmd(t *testing.T) {
+	workDir := t.TempDir()
+
+	// Prepare content to be backed up
+	locationDir := path.Join(workDir, "my-location")
+	err := os.Mkdir(locationDir, 0750)
+	assert.Nil(t, err)
+	err = os.WriteFile(path.Join(locationDir, "back-me-up.txt"), []byte("hello world"), 0640)
+	assert.Nil(t, err)
+
+	// Write config file
+	config, err := yaml.Marshal(map[string]interface{}{
+		"version": 2,
+		"locations": map[string]map[string]interface{}{
+			"my-location": {
+				"type": "local",
+				"from": []string{locationDir},
+				"to":   []string{"test"},
+			},
+		},
+		"backends": map[string]map[string]interface{}{
+			"test": {
+				"type": "local",
+				"path": path.Join(workDir, "test-backend"),
+				"key":  "supersecret",
+			},
+		},
+	})
+	assert.Nil(t, err)
+	configPath := path.Join(workDir, ".autorestic.yml")
+	err = os.WriteFile(configPath, config, 0640)
+	assert.Nil(t, err)
+
+	// Init repo (not initialized by default)
+	err = runCmd(t, "exec", "--ci", "-a", "-c", configPath, "init")
+	assert.Nil(t, err)
+
+	// Do the backup
+	err = runCmd(t, "backup", "--ci", "-a", "-c", configPath)
+	assert.Nil(t, err)
+
+	// Restore in a separate dir
+	restoreDir := path.Join(workDir, "restore")
+	err = runCmd(t, "restore", "--ci", "-c", configPath, "-l", "my-location", "--to", restoreDir)
+	assert.Nil(t, err)
+
+	// Check restored file
+	restoredContent, err := os.ReadFile(path.Join(restoreDir, locationDir, "back-me-up.txt"))
+	assert.Nil(t, err)
+	assert.Equal(t, "hello world", string(restoredContent))
+}
diff --git a/docs/pages/backend/index.md b/docs/pages/backend/index.md
index 01a14fa..545abcf 100644
--- a/docs/pages/backend/index.md
+++ b/docs/pages/backend/index.md
@@ -38,3 +38,19 @@ backends:
 ```
 
 With this setting, if a key is missing, `autorestic` will crash instead of generating a new key and updating your config file.
+
+## Automatic Backend Initialization
+
+`autorestic` is able to automatically initialize backends for you. This is done by setting `init: true` in the config for a given backend. For example:
+
+```yaml | .autorestic.yml
+backend:
+  foo:
+    type: ...
+    path: ...
+    init: true
+```
+
+When you set `init: true` on a backend config, `autorestic` will automatically initialize the underlying `restic` repository that powers the backend if it's not already initialized. In practice, this means that the backend will be initialized the first time it is being backed up to.
+
+This option is helpful in cases where you want to automate the configuration of `autorestic`. This means that instead of running `autorestic exec init -b ...` manually when you create a new backend, you can let `autorestic` initialize it for you.
diff --git a/go.mod b/go.mod
index 8b1d3e9..577a95d 100644
--- a/go.mod
+++ b/go.mod
@@ -12,6 +12,7 @@ require (
 	github.com/spf13/cobra v1.4.0
 	github.com/spf13/viper v1.11.0
 	github.com/stretchr/testify v1.9.0
+	gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
@@ -35,5 +36,4 @@ require (
 	golang.org/x/text v0.3.8 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/internal/backend.go b/internal/backend.go
index 669935a..fd76990 100644
--- a/internal/backend.go
+++ b/internal/backend.go
@@ -24,6 +24,7 @@ type Backend struct {
 	Path       string            `mapstructure:"path,omitempty"`
 	Key        string            `mapstructure:"key,omitempty"`
 	RequireKey bool              `mapstructure:"requireKey,omitempty"`
+	Init       bool              `mapstructure:"init,omitempty"`
 	Env        map[string]string `mapstructure:"env,omitempty"`
 	Rest       BackendRest       `mapstructure:"rest,omitempty"`
 	Options    Options           `mapstructure:"options,omitempty"`
@@ -130,20 +131,44 @@ func (b Backend) validate() error {
 		return err
 	}
 	options := ExecuteOptions{Envs: env, Silent: true}
-	// Check if already initialized
+
+	err = b.EnsureInit()
+	if err != nil {
+		return err
+	}
+
 	cmd := []string{"check"}
 	cmd = append(cmd, combineBackendOptions("check", b)...)
 	_, _, err = ExecuteResticCommand(options, cmd...)
-	if err == nil {
-		return nil
-	} else {
-		// If not initialize
-		colors.Body.Printf("Initializing backend \"%s\"...\n", b.name)
-		cmd := []string{"init"}
-		cmd = append(cmd, combineBackendOptions("init", b)...)
-		_, _, err := ExecuteResticCommand(options, cmd...)
+	return err
+}
+
+// EnsureInit initializes the backend if it is not already initialized
+func (b Backend) EnsureInit() error {
+	env, err := b.getEnv()
+	if err != nil {
 		return err
 	}
+	options := ExecuteOptions{Envs: env, Silent: true}
+
+	checkInitCmd := []string{"cat", "config"}
+	checkInitCmd = append(checkInitCmd, combineBackendOptions("cat", b)...)
+	_, _, err = ExecuteResticCommand(options, checkInitCmd...)
+
+	// Note that `restic` has a special exit code (10) to indicate that the
+	// repository does not exist. This exit code was introduced in `restic@0.17.0`
+	// on 2024-07-26. We're not using it here because this is a too recent and
+	// people on older versions of `restic` won't have this feature work  correctly.
+	// See: https://restic.readthedocs.io/en/latest/075_scripting.html#exit-codes
+	if err != nil {
+		colors.Body.Printf("Initializing backend \"%s\"...\n", b.name)
+		initCmd := []string{"init"}
+		initCmd = append(initCmd, combineBackendOptions("init", b)...)
+		_, _, err := ExecuteResticCommand(options, initCmd...)
+		return err
+	}
+
+	return err
 }
 
 func (b Backend) Exec(args []string) error {
diff --git a/internal/backend_test.go b/internal/backend_test.go
index e65e4fe..508297f 100644
--- a/internal/backend_test.go
+++ b/internal/backend_test.go
@@ -3,8 +3,10 @@ package internal
 import (
 	"fmt"
 	"os"
+	"path"
 	"testing"
 
+	"github.com/cupcakearmy/autorestic/internal/flags"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
 )
@@ -263,3 +265,69 @@ func TestValidate(t *testing.T) {
 		assert.EqualError(t, err, "backend foo requires a key but none was provided")
 	})
 }
+
+func TestValidateInitsRepo(t *testing.T) {
+	// This is normally initialized by the cobra commands but they don't run in
+	// this test so we do it ourselves.
+	flags.RESTIC_BIN = "restic"
+
+	workDir := t.TempDir()
+
+	b := Backend{
+		name: "test",
+		Type: "local",
+		Path: path.Join(workDir, "backend"),
+		Key:  "supersecret",
+	}
+
+	config = &Config{Backends: map[string]Backend{"test": b}}
+	defer func() { config = nil }()
+
+	// Check should fail because the repo doesn't exist
+	err := b.Exec([]string{"check"})
+	assert.Error(t, err)
+
+	err = b.validate()
+	assert.NoError(t, err)
+
+	// Check should pass now
+	err = b.Exec([]string{"check"})
+	assert.NoError(t, err)
+}
+
+func TestEnsureInit(t *testing.T) {
+	// This is normally initialized by the cobra commands but they don't run in
+	// this test so we do it ourselves.
+	flags.RESTIC_BIN = "restic"
+
+	workDir := t.TempDir()
+
+	b := Backend{
+		name: "test",
+		Type: "local",
+		Path: path.Join(workDir, "backend"),
+		Key:  "supersecret",
+	}
+
+	config = &Config{Backends: map[string]Backend{"test": b}}
+	defer func() { config = nil }()
+
+	// Check should fail because the repo doesn't exist
+	err := b.Exec([]string{"check"})
+	assert.Error(t, err)
+
+	err = b.EnsureInit()
+	assert.NoError(t, err)
+
+	// Check should pass now
+	err = b.Exec([]string{"check"})
+	assert.NoError(t, err)
+
+	// Run again to make sure it's idempotent
+	err = b.EnsureInit()
+	assert.NoError(t, err)
+
+	// Check should still pass
+	err = b.Exec([]string{"check"})
+	assert.NoError(t, err)
+}
diff --git a/internal/location.go b/internal/location.go
index ce970ec..425f601 100644
--- a/internal/location.go
+++ b/internal/location.go
@@ -223,6 +223,14 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
 			continue
 		}
 
+		if backend.Init {
+			err = backend.EnsureInit()
+			if err != nil {
+				errors = append(errors, err)
+				continue
+			}
+		}
+
 		cmd := []string{"backup"}
 		cmd = append(cmd, combineAllOptions("backup", l, backend)...)
 		if cron {