diff --git a/etc/freebsd-update.conf b/etc/freebsd-update.conf
index af53be4cb62b..44109034b007 100644
--- a/etc/freebsd-update.conf
+++ b/etc/freebsd-update.conf
@@ -63,3 +63,14 @@ MergeChanges /etc/ /var/named/etc/ /boot/device.hints
 # which *might* be installed of which FreeBSD Update should figure out
 # which actually are installed and upgrade those (StrictComponents no)?
 # StrictComponents no
+
+# When installing a new kernel perform a backup of the old one first
+# so it is possible to boot the old kernel in case of problems.
+# BackupKernel yes
+
+# If BackupKernel is enabled, the backup kernel is saved to this
+# directory.
+# BackupKernelDir /boot/kernel.old
+
+# When backing up a kernel also back up debug symbol files?
+# BackupKernelSymbolFiles no
diff --git a/share/man/man5/freebsd-update.conf.5 b/share/man/man5/freebsd-update.conf.5
index 29775fdb276f..d3721028e260 100644
--- a/share/man/man5/freebsd-update.conf.5
+++ b/share/man/man5/freebsd-update.conf.5
@@ -25,7 +25,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd August 30, 2006
+.Dd August 19, 2009
 .Dt FREEBSD-UPDATE.CONF 5
 .Os FreeBSD
 .Sh NAME
@@ -48,7 +48,7 @@ error.
 .Pp
 The possible options and their meanings are as follows:
 .Pp
-.Bl -tag -width "KeepModifiedMetadata"
+.Bl -tag -width ".Cm BackupKernelSymbolFiles"
 .It Cm KeyPrint
 The single parameter following this keyword is the SHA256 hash
 of the RSA key which will be trusted to sign updates.
@@ -171,6 +171,54 @@ command is used ("yes"), or merely as a list of components
 which might be installed, of which
 .Cm freebsd-update
 should identify which in fact are present ("no").
+.It Cm BackupKernel
+The single parameter following this keyword must be
+.Dq yes
+or
+.Dq no
+and specifies whether
+.Cm freebsd-update
+will create a backup of the old kernel before installing a new kernel.
+This backup kernel can be used to recover a system where the newly
+installed kernel somehow did not work.
+Note that the backup kernel is not reverted to its original state by
+the
+.Cm freebsd-update
+rollback command.
+.It Cm BackupKernelDir
+This keyword sets the directory which is used to store a backup
+kernel, if the BackupKernel feature is enabled.
+If the directory already exist, and it was not created by
+.Cm freebsd-update ,
+the directory is skipped.
+In the case of the primary directory name not being usable, a number
+starting with
+.Sq 1
+is appended to the directory name.
+Like with the primary directory name, the constructed directory name is
+only used if the path name does not exist, or if the directory was
+previously created by
+.Cm freebsd-update .
+If the constructed directory still exist the appended number is
+incremented with 1 and the directory search process restarted.
+Should the number increment go above 9,
+.Cm freebsd-update
+will abort.
+.It Cm BackupKernelSymbolFiles
+The single parameter following this keyword must be
+.Dq yes
+or
+.Dq no
+and specifies whether
+.Cm freebsd-update
+will also backup kernel symbol files, if they exist.
+The kernel symbol files takes up a lot of disk space and are not
+needed for recovery purposes.
+If the symbol files are needed, after recovering a system using the
+backup kernel, the
+.Cm freebsd-update
+rollback command will recreate the symbol files along with the old
+kernel.
 .El
 .Sh FILES
 .Bl -tag -width "/etc/freebsd-update.conf"
diff --git a/usr.sbin/freebsd-update/freebsd-update.sh b/usr.sbin/freebsd-update/freebsd-update.sh
index 331ef1014947..2eacca8d2fb4 100644
--- a/usr.sbin/freebsd-update/freebsd-update.sh
+++ b/usr.sbin/freebsd-update/freebsd-update.sh
@@ -88,7 +88,7 @@ EOF
 CONFIGOPTIONS="KEYPRINT WORKDIR SERVERNAME MAILTO ALLOWADD ALLOWDELETE
     KEEPMODIFIEDMETADATA COMPONENTS IGNOREPATHS UPDATEIFUNMODIFIED
     BASEDIR VERBOSELEVEL TARGETRELEASE STRICTCOMPONENTS MERGECHANGES
-    IDSIGNOREPATHS"
+    IDSIGNOREPATHS BACKUPKERNEL BACKUPKERNELDIR BACKUPKERNELSYMBOLFILES"
 
 # Set all the configuration options to "".
 nullconfig () {
@@ -308,6 +308,70 @@ config_VerboseLevel () {
 	fi
 }
 
+config_BackupKernel () {
+	if [ -z ${BACKUPKERNEL} ]; then
+		case $1 in
+		[Yy][Ee][Ss])
+			BACKUPKERNEL=yes
+			;;
+		[Nn][Oo])
+			BACKUPKERNEL=no
+			;;
+		*)
+			return 1
+			;;
+		esac
+	else
+		return 1
+	fi
+}
+
+config_BackupKernelDir () {
+	if [ -z ${BACKUPKERNELDIR} ]; then
+		if [ -z "$1" ]; then
+			echo "BackupKernelDir set to empty dir"
+			return 1
+		fi
+
+		# We check for some paths which would be extremely odd
+		# to use, but which could cause a lot of problems if
+		# used.
+		case $1 in
+		/|/bin|/boot|/etc|/lib|/libexec|/sbin|/usr|/var)
+			echo "BackupKernelDir set to invalid path $1"
+			return 1
+			;;
+		/*)
+			BACKUPKERNELDIR=$1
+			;;
+		*)
+			echo "BackupKernelDir ($1) is not an absolute path"
+			return 1
+			;;
+		esac
+	else
+		return 1
+	fi
+}
+
+config_BackupKernelSymbolFiles () {
+	if [ -z ${BACKUPKERNELSYMBOLFILES} ]; then
+		case $1 in
+		[Yy][Ee][Ss])
+			BACKUPKERNELSYMBOLFILES=yes
+			;;
+		[Nn][Oo])
+			BACKUPKERNELSYMBOLFILES=no
+			;;
+		*)
+			return 1
+			;;
+		esac
+	else
+		return 1
+	fi
+}
+
 # Handle one line of configuration
 configline () {
 	if [ $# -eq 0 ]; then
@@ -461,6 +525,9 @@ default_params () {
 	config_BaseDir /
 	config_VerboseLevel stats
 	config_StrictComponents no
+	config_BackupKernel yes
+	config_BackupKernelDir /boot/kernel.old
+	config_BackupKernelSymbolFiles no
 
 	# Merge these defaults into the earlier-configured settings
 	mergeconfig
@@ -665,6 +732,14 @@ install_check_params () {
 		echo "Re-run '$0 fetch'."
 		exit 1
 	fi
+
+	# Figure out what directory contains the running kernel
+	BOOTFILE=`sysctl -n kern.bootfile`
+	KERNELDIR=${BOOTFILE%/kernel}
+	if ! [ -d ${KERNELDIR} ]; then
+		echo "Cannot identify running kernel"
+		exit 1
+	fi
 }
 
 # Perform sanity checks and set some final parameters in
@@ -2494,6 +2569,88 @@ install_unschg () {
 	rm filelist
 }
 
+# Decide which directory name to use for kernel backups.
+backup_kernel_finddir () {
+	CNT=0
+	while true ; do
+		# Pathname does not exist, so it is OK use that name
+		# for backup directory.
+		if [ ! -e $BACKUPKERNELDIR ]; then
+			return 0
+		fi
+
+		# If directory do exist, we only use if it has our
+		# marker file.
+		if [ -d $BACKUPKERNELDIR -a \
+			-e $BACKUPKERNELDIR/.freebsd-update ]; then
+			return 0
+		fi
+
+		# We could not use current directory name, so add counter to
+		# the end and try again.
+		CNT=$((CNT + 1))
+		if [ $CNT -gt 9 ]; then
+			echo "Could not find valid backup dir ($BACKUPKERNELDIR)"
+			exit 1
+		fi
+		BACKUPKERNELDIR="`echo $BACKUPKERNELDIR | sed -Ee 's/[0-9]\$//'`"
+		BACKUPKERNELDIR="${BACKUPKERNELDIR}${CNT}"
+	done
+}
+
+# Backup the current kernel using hardlinks, if not disabled by user.
+# Since we delete all files in the directory used for previous backups
+# we create a marker file called ".freebsd-update" in the directory so
+# we can determine on the next run that the directory was created by
+# freebsd-update and we then do not accidentally remove user files in
+# the unlikely case that the user has created a directory with a
+# conflicting name.
+backup_kernel () {
+	# Only make kernel backup is so configured.
+	if [ $BACKUPKERNEL != yes ]; then
+		return 0
+	fi
+
+	# Decide which directory name to use for kernel backups.
+	backup_kernel_finddir
+
+	# Remove old kernel backup files.  If $BACKUPKERNELDIR was
+	# "not ours", backup_kernel_finddir would have exited, so
+	# deleting the directory content is as safe as we can make it.
+	if [ -d $BACKUPKERNELDIR ]; then
+		rm -f $BACKUPKERNELDIR/*
+	fi
+
+	# Create directory for backup if it doesn't exist.
+	mkdir -p $BACKUPKERNELDIR
+
+	# Mark the directory as having been created by freebsd-update.
+	touch $BACKUPKERNELDIR/.freebsd-update
+	if [ $? -ne 0 ]; then
+		echo "Could not create kernel backup directory"
+		exit 1
+	fi
+
+	# Disable pathname expansion to be sure *.symbols is not
+	# expanded.
+	set -f
+
+	# Use find to ignore symbol files, unless disabled by user.
+	if [ $BACKUPKERNELSYMBOLFILES = yes ]; then
+		FINDFILTER=""
+	else
+		FINDFILTER=-"a ! -name *.symbols"
+	fi
+
+	# Backup all the kernel files using hardlinks.
+	find $KERNELDIR -type f $FINDFILTER | \
+		sed -Ee "s,($KERNELDIR)/?(.*),\1/\2 ${BACKUPKERNELDIR}/\2," | \
+		xargs -n 2 cp -pl
+
+	# Re-enable patchname expansion.
+	set +f
+}
+
 # Install new files
 install_from_index () {
 	# First pass: Do everything apart from setting file flags.  We
@@ -2575,6 +2732,9 @@ install_files () {
 		grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
 		grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
 
+		# Backup current kernel before installing a new one
+		backup_kernel || return 1
+
 		# Install new files
 		install_from_index INDEX-NEW || return 1