diff --git a/.markdownlint.json b/.markdownlint.json
index 41b3c04fd..d83a2dcdf 100644
--- a/.markdownlint.json
+++ b/.markdownlint.json
@@ -1,6 +1,7 @@
 {
   "default": true,
   "MD033": false,
+  "MD041": false,
   "MD013": {
     "tables": false,
     "line_length": 1000
diff --git a/contrib/ci/dependencies.xml b/contrib/ci/dependencies.xml
index d360fc291..9455d7a81 100644
--- a/contrib/ci/dependencies.xml
+++ b/contrib/ci/dependencies.xml
@@ -1078,6 +1078,24 @@
       
     
   
+  
+  
+    
+      
+      
+      
+    
+    
+      
+      
+    
+    
+      
+    
+    
+      pandoc-cli
+    
+  
   
     
       
diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in
index f1e57171e..99fc93aa6 100644
--- a/contrib/fwupd.spec.in
+++ b/contrib/fwupd.spec.in
@@ -77,6 +77,7 @@ BuildRequires: libarchive-devel
 BuildRequires: libcbor-devel
 BuildRequires: gobject-introspection-devel
 BuildRequires: gcab
+BuildRequires: pandoc
 %ifarch %{valgrind_arches}
 BuildRequires: valgrind
 BuildRequires: valgrind-devel
diff --git a/meson.build b/meson.build
index 8d3ae3a02..a37aca6e8 100644
--- a/meson.build
+++ b/meson.build
@@ -273,6 +273,9 @@ if libgcab.type_name() == 'pkgconfig'
   endif
 endif
 
+# manpages
+pandoc = find_program('pandoc', required: get_option('man'))
+
 bashcomp = dependency('bash-completion', required: false)
 python3 = import('python').find_installation('python3')
 
diff --git a/meson_options.txt b/meson_options.txt
index 013d0faa5..96cf696c0 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -5,7 +5,7 @@ option('firmware-packager', type : 'boolean', value : true, description : 'enabl
 option('docs', type : 'feature', description : 'Build developer documentation', deprecated: {'docgen': 'enabled', 'none': 'disabled'})
 option('introspection', type : 'feature', description : 'generate GObject Introspection data', deprecated: {'true': 'enabled', 'false': 'disabled'})
 option('lvfs', type : 'combo', choices : ['true', 'false', 'disabled'], value : 'true', description : 'install LVFS remotes')
-option('man', type : 'boolean', value : true, description : 'enable man pages')
+option('man', type : 'feature', deprecated: {'true': 'enabled', 'false': 'disabled'}, description : 'man pages')
 option('libarchive', type : 'feature', description : 'libarchive support', deprecated: {'true': 'enabled', 'false': 'disabled'})
 option('gudev', type : 'feature', description : 'GUdev support', deprecated: {'true': 'enabled', 'false': 'disabled'})
 option('gusb', type : 'feature', description : 'GUsb support', deprecated: {'true': 'enabled', 'false': 'disabled'})
diff --git a/plugins/dfu/dfu-tool.1 b/plugins/dfu/dfu-tool.1
deleted file mode 100644
index 831d2e146..000000000
--- a/plugins/dfu/dfu-tool.1
+++ /dev/null
@@ -1,31 +0,0 @@
-.\" Report problems in https://github.com/fwupd/fwupd
-.TH man 1 "11 April 2021" @PACKAGE_VERSION@ "dfu-tool man page"
-.SH NAME
-dfu-tool \- write firmware to DFU devices
-.SH SYNOPSIS
-dfu-tool [CMD]
-.SH DESCRIPTION
-.PP
-This manual page documents briefly the \fBdfu-tool\fR command.
-.PP
-\fBdfu-tool\fR allows a user to write various kinds of
-firmware onto devices supporting the USB Device Firmware Upgrade protocol.
-This tool can be used to switch the device from the normal runtime mode
-to `DFU mode' which allows the user to read and write firmware.
-Either the whole device can be written in one operation, or individual
-`targets' can be specified with the alternative name or number.
-.PP
-All synchronous actions can be safely cancelled and on failure will return
-errors with both a type and a full textual description.
-libdfu supports DFU 1.0, DFU 1.1 and the ST DfuSe vendor extension, and
-handles many device `quirks' necessary for the real-world implementations
-of DFU\&.
-.SH OPTIONS
-The dfu-tool command takes various options depending on the action.
-Run \fBdfu-tool --help\fR for the full list.
-.SH SEE ALSO
-fwupdtool(1), fwupdmgr(1)
-.SH BUGS
-No known bugs.
-.SH AUTHOR
-Richard Hughes (richard@hughsie.com)
diff --git a/plugins/dfu/dfu-tool.md b/plugins/dfu/dfu-tool.md
new file mode 100644
index 000000000..724a72cdb
--- /dev/null
+++ b/plugins/dfu/dfu-tool.md
@@ -0,0 +1,45 @@
+% dfu-tool(1) @PACKAGE_VERSION@ | dfu-tool man page
+
+NAME
+====
+
+**dfu-tool** — write firmware to DFU devices
+
+SYNOPSIS
+========
+
+| **dfu-tool** [CMD]
+
+DESCRIPTION
+===========
+
+This manual page documents briefly the **dfu-tool** command.
+
+**dfu-tool** allows a user to write various kinds of
+firmware onto devices supporting the USB Device Firmware Upgrade protocol.
+This tool can be used to switch the device from the normal runtime mode
+to DFU mode which allows the user to read and write firmware.
+Either the whole device can be written in one operation, or individual
+targets can be specified with the alternative name or number.
+
+All synchronous actions can be safely cancelled and on failure will return
+errors with both a type and a full textual description.
+libdfu supports DFU 1.0, DFU 1.1 and the ST DfuSe vendor extension, and
+handles many device quirks necessary for the real-world implementations
+of DFU.
+
+OPTIONS
+=======
+
+The dfu-tool command takes various options depending on the action.
+Run **dfu-tool --help** for the full list.
+
+BUGS
+====
+
+See GitHub Issues: 
+
+SEE ALSO
+========
+
+fwupdtool(1), fwupdmgr(1)
diff --git a/plugins/dfu/meson.build b/plugins/dfu/meson.build
index 1d51df061..380a4389b 100644
--- a/plugins/dfu/meson.build
+++ b/plugins/dfu/meson.build
@@ -42,11 +42,20 @@ fu_dfu_tool = executable(
 )
 endif
 
-if get_option('compat_cli') and get_option('man')
-  configure_file(
-    input: 'dfu-tool.1',
+if get_option('compat_cli') and pandoc.found()
+  conf_man = configuration_data()
+  conf_man.set('PACKAGE_VERSION', fwupd_version)
+  dfu_tool_md = configure_file(
+    input: 'dfu-tool.md',
+    output: 'dfu-tool.md',
+    configuration: conf_man,
+  )
+  custom_target('dfu-tool.1',
+    input: dfu_tool_md,
     output: 'dfu-tool.1',
-    configuration: conf,
+    command: [
+      pandoc, '--standalone', '--to', 'man', '@INPUT@', '-o', '@OUTPUT@',
+    ],
     install: true,
     install_dir: join_paths(mandir, 'man1'),
   )
diff --git a/plugins/uefi-capsule/fwupdate.1 b/plugins/uefi-capsule/fwupdate.1
deleted file mode 100644
index 513b14ca1..000000000
--- a/plugins/uefi-capsule/fwupdate.1
+++ /dev/null
@@ -1,17 +0,0 @@
-.\" Report problems in https://github.com/fwupd/fwupd
-.TH man 1 "27 May 2022" @PACKAGE_VERSION@ "fwupdate man page"
-.SH NAME
-fwupdate \-debugging utility for UEFI firmware updates
-.SH SYNOPSIS
-fwupdate [CMD]
-.SH DESCRIPTION
-fwupdate is a deprecated tool that allows deploying capsule updates.
-.SH OPTIONS
-The fwupdate command takes various options depending on the action.
-Run \fBfwupdate --help\fR for the full list.
-.SH SEE ALSO
-fwupdtool(1), fwupdmgr(1)
-.SH BUGS
-No known bugs.
-.SH AUTHOR
-Richard Hughes (richard@hughsie.com)
diff --git a/plugins/uefi-capsule/fwupdate.md b/plugins/uefi-capsule/fwupdate.md
new file mode 100644
index 000000000..084a7c495
--- /dev/null
+++ b/plugins/uefi-capsule/fwupdate.md
@@ -0,0 +1,32 @@
+% fwupdate(1) @PACKAGE_VERSION@ | fwupdate man page
+
+NAME
+====
+
+**fwupdate** — debugging utility for UEFI firmware updates
+
+SYNOPSIS
+========
+
+| **fwupdate** \[CMD]
+
+DESCRIPTION
+===========
+
+**fwupdate** is a deprecated tool that allows deploying capsule updates.
+
+OPTIONS
+=======
+
+The fwupdate command takes various options depending on the action.
+Run **fwupdate --help** for the full list.
+
+BUGS
+====
+
+See GitHub Issues: 
+
+SEE ALSO
+========
+
+**fwupdmr(1)**, **fwupdtool(1)**
diff --git a/plugins/uefi-capsule/meson.build b/plugins/uefi-capsule/meson.build
index 35b351b82..788f626c7 100644
--- a/plugins/uefi-capsule/meson.build
+++ b/plugins/uefi-capsule/meson.build
@@ -77,11 +77,20 @@ fwupdate = executable(
 )
 endif
 
-if get_option('compat_cli') and get_option('man')
-  configure_file(
-    input: 'fwupdate.1',
+if get_option('compat_cli') and pandoc.found()
+  conf_man = configuration_data()
+  conf_man.set('PACKAGE_VERSION', fwupd_version)
+  fwupdate_md = configure_file(
+    input: 'fwupdate.md',
+    output: 'fwupdate.md',
+    configuration: conf_man,
+  )
+  custom_target('fwupdate.1',
+    input: fwupdate_md,
     output: 'fwupdate.1',
-    configuration: conf,
+    command: [
+      pandoc, '--standalone', '--to', 'man', '@INPUT@', '-o', '@OUTPUT@',
+    ],
     install: true,
     install_dir: join_paths(mandir, 'man1'),
   )
diff --git a/plugins/uefi-dbx/dbxtool.1 b/plugins/uefi-dbx/dbxtool.1
deleted file mode 100644
index a518acd3c..000000000
--- a/plugins/uefi-dbx/dbxtool.1
+++ /dev/null
@@ -1,22 +0,0 @@
-.\" Report problems in https://github.com/fwupd/fwupd
-.TH man 1 "11 April 2021" @PACKAGE_VERSION@ "dbxtool man page"
-.SH NAME
-dbxtool \- modify the dbx revocation list
-.SH SYNOPSIS
-dbxtool [CMD]
-.SH DESCRIPTION
-.PP
-This manual page documents briefly the \fBdbxtool\fR command.
-.PP
-\fBdbxtool\fR allows a user to operate on the UEFI dbx revocation list.
-This tool can be used to list the current dbx contents or update it to a newer
-version.
-.SH OPTIONS
-The dbxtool command takes various options depending on the action.
-Run \fBdbxtool --help\fR for the full list.
-.SH SEE ALSO
-fwupdmgr(1)
-.SH BUGS
-No known bugs.
-.SH AUTHOR
-Richard Hughes (richard@hughsie.com)
diff --git a/plugins/uefi-dbx/dbxtool.md b/plugins/uefi-dbx/dbxtool.md
new file mode 100644
index 000000000..a94d95bba
--- /dev/null
+++ b/plugins/uefi-dbx/dbxtool.md
@@ -0,0 +1,35 @@
+% dbxtool(1) @PACKAGE_VERSION@ | dbxtool man page
+
+NAME
+====
+
+**dbxtool** — modify the dbx revocation list
+
+SYNOPSIS
+========
+
+| **dbxtool** [CMD]
+
+DESCRIPTION
+===========
+
+This manual page documents briefly the **dbxtool** command.
+
+**dbxtool** allows a user to operate on the UEFI dbx revocation list.
+This tool can be used to list the current dbx contents or update it to a newer version.
+
+OPTIONS
+=======
+
+The dbxtool command takes various options depending on the action.
+Run **dbxtool --help** for the full list.
+
+BUGS
+====
+
+See GitHub Issues: 
+
+SEE ALSO
+========
+
+fwupdtool(1), fwupdmgr(1)
diff --git a/plugins/uefi-dbx/meson.build b/plugins/uefi-dbx/meson.build
index 60ae5a41b..3932b7244 100644
--- a/plugins/uefi-dbx/meson.build
+++ b/plugins/uefi-dbx/meson.build
@@ -56,11 +56,20 @@ dbxtool = executable(
   c_args: cargs,
 )
 
-if get_option('man')
-  configure_file(
-    input: 'dbxtool.1',
+if pandoc.found()
+  conf_man = configuration_data()
+  conf_man.set('PACKAGE_VERSION', fwupd_version)
+  dbxtool_md = configure_file(
+    input: 'dbxtool.md',
+    output: 'dbxtool.md',
+    configuration: conf_man,
+  )
+  custom_target('dbxtool.1',
+    input: dbxtool_md,
     output: 'dbxtool.1',
-    configuration: conf,
+    command: [
+      pandoc, '--standalone', '--to', 'man', '@INPUT@', '-o', '@OUTPUT@',
+    ],
     install: true,
     install_dir: join_paths(mandir, 'man1'),
   )
diff --git a/src/fwupdagent.1 b/src/fwupdagent.1
deleted file mode 100644
index 23406d595..000000000
--- a/src/fwupdagent.1
+++ /dev/null
@@ -1,18 +0,0 @@
-.\" Report problems in https://github.com/fwupd/fwupd
-.TH man 1 "11 April 2021" @PACKAGE_VERSION@ "fwupdagent man page"
-.SH NAME
-fwupdagent \- firmware updating agent
-.SH SYNOPSIS
-fwupdagent [CMD]
-.SH DESCRIPTION
-fwupdagent used to be a command line fwupd client intended to be used by scripts.
-You should now use the 100% compatible \fBfwupdmgr --json\fR command instead.
-The output is JSON and guaranteed to be stable.
-.SH EXIT STATUS
-Commands that successfully execute will return "0".
-.SH SEE ALSO
-fwupdmgr(1), fwupdtool(1)
-.SH BUGS
-No known bugs.
-.SH AUTHOR
-Richard Hughes (richard@hughsie.com)
diff --git a/src/fwupdagent.md b/src/fwupdagent.md
new file mode 100644
index 000000000..79e2184f5
--- /dev/null
+++ b/src/fwupdagent.md
@@ -0,0 +1,33 @@
+% fwupdagent(1) @PACKAGE_VERSION@ | fwupdagent man page
+
+NAME
+====
+
+**fwupdagent** — firmware updating agent
+
+SYNOPSIS
+========
+
+| **fwupdagent** [CMD]
+
+DESCRIPTION
+===========
+
+fwupdagent used to be a command line fwupd client intended to be used by scripts.
+You should now use the 100% compatible **fwupdmgr \-\-json** command instead.
+The output is JSON and guaranteed to be stable.
+
+EXIT STATUS
+===========
+
+Commands that successfully execute will return "0".
+
+BUGS
+====
+
+See GitHub Issues: 
+
+SEE ALSO
+========
+
+fwupdmgr(1), fwupdtool(1)
diff --git a/src/fwupdmgr.1 b/src/fwupdmgr.1
deleted file mode 100644
index 17a06807a..000000000
--- a/src/fwupdmgr.1
+++ /dev/null
@@ -1,21 +0,0 @@
-.\" Report problems in https://github.com/fwupd/fwupd
-.TH man 1 "11 April 2021" @PACKAGE_VERSION@ "fwupdmgr man page"
-.SH NAME
-fwupdmgr \- firmware update manager client utility
-.SH SYNOPSIS
-fwupdmgr [CMD]
-.SH DESCRIPTION
-fwupdmgr is a command line fwupd client intended to be used interactively.
-The output between versions of fwupd is not guaranteed to be stable.
-.SH OPTIONS
-The fwupdmgr command takes various options depending on the action.
-Run \fBfwupdmgr --help\fR for the full list.
-.SH EXIT STATUS
-Commands that successfully execute will return "0", but commands that have no
-actions but successfully execute will return "2".
-.SH SEE ALSO
-fwupdagent(1), fwupdtool(1)
-.SH BUGS
-No known bugs.
-.SH AUTHOR
-Richard Hughes (richard@hughsie.com)
diff --git a/src/fwupdmgr.md b/src/fwupdmgr.md
new file mode 100644
index 000000000..b53e03213
--- /dev/null
+++ b/src/fwupdmgr.md
@@ -0,0 +1,40 @@
+% fwupdmgr(1) @PACKAGE_VERSION@ | fwupdmgr man page
+
+NAME
+====
+
+**fwupdmgr** — firmware update manager client utility
+
+SYNOPSIS
+========
+
+| **fwupdmgr** [CMD]
+
+DESCRIPTION
+===========
+
+fwupdmgr is a command line fwupd client intended to be used interactively.
+The terminal output between versions of fwupd is not guaranteed to be stable, but if you plan on
+parsing the results then adding **\-\-json** might be just what you need.
+
+OPTIONS
+=======
+
+The fwupdmgr command takes various options depending on the action.
+Run **fwupdmgr \-\-help** for the full list.
+
+EXIT STATUS
+===========
+
+Commands that successfully execute will return "0", but commands that have no
+actions but successfully execute will return "2".
+
+BUGS
+====
+
+See GitHub Issues: 
+
+SEE ALSO
+========
+
+fwupdtool(1)
diff --git a/src/fwupdtool.1 b/src/fwupdtool.1
deleted file mode 100644
index 06a453a3c..000000000
--- a/src/fwupdtool.1
+++ /dev/null
@@ -1,25 +0,0 @@
-.\" Report problems in https://github.com/fwupd/fwupd
-.TH man 1 "11 April 2021" @PACKAGE_VERSION@ "fwupdtool man page"
-.SH NAME
-fwupdtool \- standalone firmware update utility
-.SH SYNOPSIS
-fwupdtool [CMD]
-.SH DESCRIPTION
-This tool allows an administrator to use fwupd plugins directly without using the daemon process.
-.PP
-Additionally \fBfwupdtool\fR can be used to convert firmware from various different formats,
-or to modify the images contained inside the container firmware file.
-For example, you can convert DFU or Intel HEX firmware into the vendor-specific format.
-.SH OPTIONS
-The fwupdtool command takes various options depending on the action.
-Run \fBfwupdtool --help\fR for the full list.
-Note that some runtimes failures can be ignored using \fB--force\fR.
-.SH EXIT STATUS
-Commands that successfully execute will return "0", but commands that have no
-actions but successfully execute will return "2".
-.SH SEE ALSO
-fwupdagent(1), fwupdmgr(1)
-.SH BUGS
-No known bugs.
-.SH AUTHOR
-Richard Hughes (richard@hughsie.com)
diff --git a/src/fwupdtool.md b/src/fwupdtool.md
new file mode 100644
index 000000000..57e6aa3bb
--- /dev/null
+++ b/src/fwupdtool.md
@@ -0,0 +1,45 @@
+% fwupdtool(1) @PACKAGE_VERSION@ | standalone firmware update utility man page
+
+NAME
+====
+
+**fwupdtool** — standalone firmware update utility
+
+SYNOPSIS
+========
+
+| **fwupdtool** [CMD]
+
+DESCRIPTION
+===========
+
+This tool allows an administrator to use fwupd plugins directly without using the daemon process,
+which may be faster or easier to use when creating or debugging specific plugins.
+For most end-users, **fwupdmgr** is a more suitable program to use in almost all cases.
+
+Additionally **fwupdtool** can be used to convert firmware from various different formats,
+or to modify the images contained inside the container firmware file.
+For example, you can convert DFU or Intel HEX firmware into the vendor-specific format.
+
+OPTIONS
+=======
+
+The fwupdtool command takes various options depending on the action.
+Run **fwupdtool \-\-help** for the full list.
+Note that some runtimes failures can be ignored using **\-\-force**.
+
+EXIT STATUS
+===========
+
+Commands that successfully execute will return "0", but commands that have no
+actions but successfully execute will return "2".
+
+BUGS
+====
+
+See GitHub Issues: 
+
+SEE ALSO
+========
+
+fwupdmgr(1)
diff --git a/src/meson.build b/src/meson.build
index a53e6000f..0dc9d35b5 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -245,30 +245,53 @@ fwupdtool = executable(
   install_dir: bindir
 )
 
-if get_option('man')
+if pandoc.found()
+  conf_man = configuration_data()
+  conf_man.set('PACKAGE_VERSION', fwupd_version)
   if build_daemon
-    configure_file(
-      input: 'fwupdmgr.1',
+    fwupdmgr_md = configure_file(
+      input: 'fwupdmgr.md',
+      output: 'fwupdmgr.md',
+      configuration: conf_man,
+    )
+    custom_target('fwupdmgr.1',
+      input: fwupdmgr_md,
       output: 'fwupdmgr.1',
-      configuration: conf,
+      command: [
+        pandoc, '--standalone', '--to', 'man', '@INPUT@', '-o', '@OUTPUT@',
+      ],
+      install: true,
+      install_dir: join_paths(mandir, 'man1'),
+    )
+  endif
+  if build_daemon and get_option('compat_cli')
+    fwupdagent_md = configure_file(
+      input: 'fwupdagent.md',
+      output: 'fwupdagent.md',
+      configuration: conf_man,
+    )
+    custom_target('fwupdagent.1',
+      input: fwupdagent_md,
+      output: 'fwupdagent.1',
+      command: [
+        pandoc, '--standalone', '--to', 'man', '@INPUT@', '-o', '@OUTPUT@',
+      ],
       install: true,
       install_dir: join_paths(mandir, 'man1'),
     )
-    if get_option('compat_cli')
-      configure_file(
-        input: 'fwupdagent.1',
-        output: 'fwupdagent.1',
-        configuration: conf,
-        install: true,
-        install_dir: join_paths(mandir, 'man1'),
-      )
-    endif
   endif
   if build_standalone
-    configure_file(
-      input: 'fwupdtool.1',
+    fwupdtool_md = configure_file(
+      input: 'fwupdtool.md',
+      output: 'fwupdtool.md',
+      configuration: conf_man,
+    )
+    custom_target('fwupdtool.1',
+      input: fwupdtool_md,
       output: 'fwupdtool.1',
-      configuration: conf,
+      command: [
+        pandoc, '--standalone', '--to', 'man', '@INPUT@', '-o', '@OUTPUT@',
+      ],
       install: true,
       install_dir: join_paths(mandir, 'man1'),
     )