diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 338ff005995d5..ad2c7d7609bb8 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1589,11 +1589,6 @@ impl Symbol {
         self == kw::Try
     }
 
-    /// Used for sanity checking rustdoc keyword sections.
-    pub fn is_doc_keyword(self) -> bool {
-        self <= kw::Union
-    }
-
     /// A keyword or reserved identifier that can be used as a path segment.
     pub fn is_path_segment_keyword(self) -> bool {
         self == kw::Super
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index e76ca1022a943..8babc90793a7c 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -162,18 +162,30 @@ impl Clean<ExternalCrate> for CrateNum {
                 .collect()
         };
 
+        let get_span =
+            |attr: &ast::NestedMetaItem| Some(attr.meta_item()?.name_value_literal()?.span);
+
         let as_keyword = |res: Res| {
             if let Res::Def(DefKind::Mod, def_id) = res {
                 let attrs = cx.tcx.get_attrs(def_id).clean(cx);
                 let mut keyword = None;
                 for attr in attrs.lists(sym::doc) {
-                    if let Some(v) = attr.value_str() {
-                        if attr.has_name(sym::keyword) {
-                            if v.is_doc_keyword() {
-                                keyword = Some(v.to_string());
-                                break;
+                    if attr.has_name(sym::keyword) {
+                        if let Some(v) = attr.value_str() {
+                            let k = v.to_string();
+                            if !rustc_lexer::is_ident(&k) {
+                                let sp = get_span(&attr).unwrap_or_else(|| attr.span());
+                                cx.tcx
+                                    .sess
+                                    .struct_span_err(
+                                        sp,
+                                        &format!("`{}` is not a valid identifier", v),
+                                    )
+                                    .emit();
+                            } else {
+                                keyword = Some(k);
                             }
-                            // FIXME: should warn on unknown keywords?
+                            break;
                         }
                     }
                 }
diff --git a/src/test/rustdoc-ui/invalid-keyword.rs b/src/test/rustdoc-ui/invalid-keyword.rs
new file mode 100644
index 0000000000000..ce2abc69bbd28
--- /dev/null
+++ b/src/test/rustdoc-ui/invalid-keyword.rs
@@ -0,0 +1,4 @@
+#![feature(doc_keyword)]
+
+#[doc(keyword = "foo df")] //~ ERROR
+mod foo {}
diff --git a/src/test/rustdoc-ui/invalid-keyword.stderr b/src/test/rustdoc-ui/invalid-keyword.stderr
new file mode 100644
index 0000000000000..8658e3825782d
--- /dev/null
+++ b/src/test/rustdoc-ui/invalid-keyword.stderr
@@ -0,0 +1,8 @@
+error: `foo df` is not a valid identifier
+  --> $DIR/invalid-keyword.rs:3:17
+   |
+LL | #[doc(keyword = "foo df")]
+   |                 ^^^^^^^^
+
+error: aborting due to previous error
+
diff --git a/src/test/rustdoc/keyword.rs b/src/test/rustdoc/keyword.rs
index db5d115c6da74..25e8b7912e772 100644
--- a/src/test/rustdoc/keyword.rs
+++ b/src/test/rustdoc/keyword.rs
@@ -14,3 +14,8 @@
 #[doc(keyword = "match")]
 /// this is a test!
 mod foo{}
+
+// @has foo/keyword.foo.html '//section[@id="main"]//div[@class="docblock"]//p' 'hello'
+#[doc(keyword = "foo")]
+/// hello
+mod bar {}