diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go
index 20c662b0d..25cbe5879 100644
--- a/cmd/commandfuncs.go
+++ b/cmd/commandfuncs.go
@@ -360,12 +360,37 @@ func cmdBuildInfo(fl Flags) (int, error) {
 }
 
 func cmdListModules(fl Flags) (int, error) {
+	packages := fl.Bool("packages")
 	versions := fl.Bool("versions")
 
+	type moduleInfo struct {
+		caddyModuleID string
+		goModule      *debug.Module
+		err           error
+	}
+	printModuleInfo := func(mi moduleInfo) {
+		fmt.Print(mi.caddyModuleID)
+		if versions && mi.goModule != nil {
+			fmt.Print(" " + mi.goModule.Version)
+		}
+		if packages && mi.goModule != nil {
+			fmt.Print(" " + mi.goModule.Path)
+			if mi.goModule.Replace != nil {
+				fmt.Print(" => " + mi.goModule.Replace.Path)
+			}
+		}
+		if mi.err != nil {
+			fmt.Printf(" [%v]", mi.err)
+		}
+		fmt.Println()
+	}
+
+	// organize modules by whether they come with the standard distribution
+	var standard, nonstandard, unknown []moduleInfo
+
 	bi, ok := debug.ReadBuildInfo()
-	if !ok || !versions {
-		// if there's no build information,
-		// just print out the modules
+	if !ok {
+		// oh well, just print the module IDs and exit
 		for _, m := range caddy.Modules() {
 			fmt.Println(m)
 		}
@@ -375,8 +400,8 @@ func cmdListModules(fl Flags) (int, error) {
 	for _, modID := range caddy.Modules() {
 		modInfo, err := caddy.GetModule(modID)
 		if err != nil {
-			// that's weird
-			fmt.Println(modID)
+			// that's weird, shouldn't happen
+			unknown = append(unknown, moduleInfo{caddyModuleID: modID, err: err})
 			continue
 		}
 
@@ -404,16 +429,40 @@ func cmdListModules(fl Flags) (int, error) {
 			}
 		}
 
-		// if we could find no matching module, just print out
-		// the module ID instead
-		if matched == nil {
-			fmt.Println(modID)
-			continue
-		}
+		caddyModGoMod := moduleInfo{caddyModuleID: modID, goModule: matched}
 
-		fmt.Printf("%s %s\n", modID, matched.Version)
+		if strings.HasPrefix(modPkgPath, caddy.ImportPath) {
+			standard = append(standard, caddyModGoMod)
+		} else {
+			nonstandard = append(nonstandard, caddyModGoMod)
+		}
 	}
 
+	if len(standard) > 0 {
+		for _, mod := range standard {
+			printModuleInfo(mod)
+		}
+	}
+	fmt.Printf("\n  Standard modules: %d\n", len(standard))
+	if len(nonstandard) > 0 {
+		if len(standard) > 0 {
+			fmt.Println()
+		}
+		for _, mod := range nonstandard {
+			printModuleInfo(mod)
+		}
+	}
+	fmt.Printf("\n  Non-standard modules: %d\n", len(nonstandard))
+	if len(unknown) > 0 {
+		if len(standard) > 0 || len(nonstandard) > 0 {
+			fmt.Println()
+		}
+		for _, mod := range unknown {
+			printModuleInfo(mod)
+		}
+	}
+	fmt.Printf("\n  Unknown modules: %d\n", len(unknown))
+
 	return caddy.ExitCodeSuccess, nil
 }
 
diff --git a/cmd/commands.go b/cmd/commands.go
index ea8402d94..e4a2b918c 100644
--- a/cmd/commands.go
+++ b/cmd/commands.go
@@ -191,10 +191,11 @@ config file; otherwise the default is assumed.`,
 	RegisterCommand(Command{
 		Name:  "list-modules",
 		Func:  cmdListModules,
-		Usage: "[--versions]",
+		Usage: "[--packages] [--versions]",
 		Short: "Lists the installed Caddy modules",
 		Flags: func() *flag.FlagSet {
 			fs := flag.NewFlagSet("list-modules", flag.ExitOnError)
+			fs.Bool("packages", false, "Print package paths")
 			fs.Bool("versions", false, "Print version information")
 			return fs
 		}(),