diff --git a/include/grpc++/alarm.h b/include/grpc++/alarm.h
index bd000cf4f798cca4d23b3b36b50bd7adca742ecc..ed8dacbc9440bfa11108219f052f61ac54f7c502 100644
--- a/include/grpc++/alarm.h
+++ b/include/grpc++/alarm.h
@@ -52,8 +52,25 @@ class Alarm : private GrpcLibraryCodegen {
         alarm_(grpc_alarm_create(cq->cq(), TimePoint<T>(deadline).raw_time(),
                                  static_cast<void*>(&tag_))) {}
 
+  /// Alarms aren't copyable.
+  Alarm(const Alarm&) = delete;
+  Alarm& operator=(const Alarm&) = delete;
+
+  /// Alarms are movable.
+  Alarm(Alarm&& rhs) : tag_(rhs.tag_), alarm_(rhs.alarm_) {
+    rhs.alarm_ = nullptr;
+  }
+  Alarm& operator=(Alarm&& rhs) {
+    tag_ = rhs.tag_;
+    alarm_ = rhs.alarm_;
+    rhs.alarm_ = nullptr;
+    return *this;
+  }
+
   /// Destroy the given completion queue alarm, cancelling it in the process.
-  ~Alarm() { grpc_alarm_destroy(alarm_); }
+  ~Alarm() {
+    if (alarm_ != nullptr) grpc_alarm_destroy(alarm_);
+  }
 
   /// Cancel a completion queue alarm. Calling this function over an alarm that
   /// has already fired has no effect.
@@ -73,7 +90,7 @@ class Alarm : private GrpcLibraryCodegen {
   };
 
   AlarmEntry tag_;
-  grpc_alarm* const alarm_;  // owned
+  grpc_alarm* alarm_;  // owned
 };
 
 }  // namespace grpc
diff --git a/test/cpp/common/alarm_cpp_test.cc b/test/cpp/common/alarm_cpp_test.cc
index 3e4999994a1943078606c15083ff4d488dc885d9..760dd7b956a469e74bd2cb24a726332db4a8daa1 100644
--- a/test/cpp/common/alarm_cpp_test.cc
+++ b/test/cpp/common/alarm_cpp_test.cc
@@ -40,6 +40,25 @@ TEST(AlarmTest, RegularExpiry) {
   EXPECT_EQ(junk, output_tag);
 }
 
+TEST(AlarmTest, Move) {
+  CompletionQueue cq;
+  void* junk = reinterpret_cast<void*>(1618033);
+  Alarm first(&cq, grpc_timeout_seconds_to_deadline(1), junk);
+  // Move constructor.
+  Alarm second(std::move(first));
+  // Moving assignment.
+  first = std::move(second);
+
+  void* output_tag;
+  bool ok;
+  const CompletionQueue::NextStatus status = cq.AsyncNext(
+      (void**)&output_tag, &ok, grpc_timeout_seconds_to_deadline(2));
+
+  EXPECT_EQ(status, CompletionQueue::GOT_EVENT);
+  EXPECT_TRUE(ok);
+  EXPECT_EQ(junk, output_tag);
+}
+
 TEST(AlarmTest, RegularExpiryChrono) {
   CompletionQueue cq;
   void* junk = reinterpret_cast<void*>(1618033);