diff --git a/sqlutil/db.go b/sqlutil/db.go
index 531683440c453fa43dca7c0c129f2173f34addbc..3b8bd3db37bd134126bd929ac772da9f5782f90f 100644
--- a/sqlutil/db.go
+++ b/sqlutil/db.go
@@ -5,6 +5,7 @@ import (
 	"database/sql"
 	"fmt"
 	"log"
+	"net/url"
 	"strings"
 
 	_ "github.com/mattn/go-sqlite3"
@@ -13,11 +14,24 @@ import (
 // DebugMigrations can be set to true to dump statements to stderr.
 var DebugMigrations bool
 
-const defaultOptions = "?cache=shared&_busy_timeout=10000&_journal=WAL&_sync=OFF"
+// See https://github.com/mattn/go-sqlite3/issues/209 for details on
+// why these default parameters were chosen. WAL mode is mandatory for
+// external litestream support.
+func defaultOptions() url.Values {
+	v := make(url.Values)
+	v.Set("cache", "shared")
+	v.Set("_journal", "WAL")
+	v.Set("_sync", "OFF")
+	v.Set("_busy_timeout", "999999")
+	v.Set("_fk", "true")
+	v.Set("_cache_size", "268435456")
+	v.Set("_auto_vacuum", "incremental")
+	return v
+}
 
 type sqlOptions struct {
 	migrations []func(*sql.Tx) error
-	sqlopts    string
+	sqlopts    url.Values
 }
 
 type Option func(*sqlOptions)
@@ -28,16 +42,16 @@ func WithMigrations(migrations []func(*sql.Tx) error) Option {
 	}
 }
 
-func WithSqliteOptions(sqlopts string) Option {
+func WithSqliteOption(opt, value string) Option {
 	return func(opts *sqlOptions) {
-		opts.sqlopts = sqlopts
+		opts.sqlopts.Set(opt, value)
 	}
 }
 
 // OpenDB opens a SQLite database and runs the database migrations.
 func OpenDB(dburi string, options ...Option) (*sql.DB, error) {
 	var opts sqlOptions
-	opts.sqlopts = defaultOptions
+	opts.sqlopts = defaultOptions()
 	for _, o := range options {
 		o(&opts)
 	}
@@ -45,7 +59,7 @@ func OpenDB(dburi string, options ...Option) (*sql.DB, error) {
 	// Add sqlite3-specific parameters if none are already
 	// specified in the connection URI.
 	if !strings.Contains(dburi, "?") {
-		dburi += opts.sqlopts
+		dburi = fmt.Sprintf("%s?%s", dburi, opts.sqlopts.Encode())
 	}
 
 	db, err := sql.Open("sqlite3", dburi)
@@ -56,6 +70,7 @@ func OpenDB(dburi string, options ...Option) (*sql.DB, error) {
 	// Limit the pool to a single connection.
 	// https://github.com/mattn/go-sqlite3/issues/209
 	db.SetMaxOpenConns(1)
+	db.SetMaxIdleConns(1)
 
 	if err = migrate(db, opts.migrations); err != nil {
 		db.Close() // nolint
diff --git a/sqlutil/db_test.go b/sqlutil/db_test.go
index e6da51c8e485570aa61c488b7f51ca266bc0dd60..add6dbb6fa5d24a0a4f36694bfca021c935dc3fe 100644
--- a/sqlutil/db_test.go
+++ b/sqlutil/db_test.go
@@ -3,7 +3,6 @@ package sqlutil
 import (
 	"context"
 	"database/sql"
-	"io/ioutil"
 	"os"
 	"testing"
 )
@@ -13,7 +12,7 @@ func init() {
 }
 
 func TestOpenDB(t *testing.T) {
-	dir, err := ioutil.TempDir("", "")
+	dir, err := os.MkdirTemp("", "")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -44,7 +43,7 @@ func checkTestValue(t *testing.T, db *sql.DB) {
 }
 
 func TestOpenDB_Migrations_MultipleStatements(t *testing.T) {
-	dir, err := ioutil.TempDir("", "")
+	dir, err := os.MkdirTemp("", "")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -64,7 +63,7 @@ func TestOpenDB_Migrations_MultipleStatements(t *testing.T) {
 }
 
 func TestOpenDB_Migrations_SingleStatement(t *testing.T) {
-	dir, err := ioutil.TempDir("", "")
+	dir, err := os.MkdirTemp("", "")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -86,7 +85,7 @@ func TestOpenDB_Migrations_SingleStatement(t *testing.T) {
 }
 
 func TestOpenDB_Migrations_Versions(t *testing.T) {
-	dir, err := ioutil.TempDir("", "")
+	dir, err := os.MkdirTemp("", "")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -114,7 +113,7 @@ func TestOpenDB_Migrations_Versions(t *testing.T) {
 }
 
 func TestOpenDB_Write(t *testing.T) {
-	dir, err := ioutil.TempDir("", "")
+	dir, err := os.MkdirTemp("", "")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -143,7 +142,7 @@ func TestOpenDB_Write(t *testing.T) {
 }
 
 func TestOpenDB_Migrations_Legacy(t *testing.T) {
-	dir, err := ioutil.TempDir("", "")
+	dir, err := os.MkdirTemp("", "")
 	if err != nil {
 		t.Fatal(err)
 	}