diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c
index 909648079c074174db365dfe461fd74e290731b4..afc9d71e19cf0a5398419433c524aeb80da074c2 100644
--- a/lib/ngtcp2_conn.c
+++ b/lib/ngtcp2_conn.c
@@ -8964,36 +8964,15 @@ fin:
   return nwrite;
 }
 
-ngtcp2_ssize ngtcp2_conn_write_connection_close(ngtcp2_conn *conn,
-                                                ngtcp2_path *path,
+static ngtcp2_ssize conn_write_connection_close(ngtcp2_conn *conn,
                                                 uint8_t *dest, size_t destlen,
+                                                uint8_t pkt_type,
                                                 uint64_t error_code,
                                                 ngtcp2_tstamp ts) {
   ngtcp2_pktns *in_pktns = conn->in_pktns;
   ngtcp2_pktns *hs_pktns = conn->hs_pktns;
   ngtcp2_ssize res = 0, nwrite;
   ngtcp2_frame fr;
-  uint8_t pkt_type;
-
-  conn->log.last_ts = ts;
-  conn->qlog.last_ts = ts;
-
-  if (conn_check_pkt_num_exhausted(conn)) {
-    return NGTCP2_ERR_PKT_NUM_EXHAUSTED;
-  }
-
-  switch (conn->state) {
-  case NGTCP2_CS_CLIENT_INITIAL:
-  case NGTCP2_CS_CLOSING:
-  case NGTCP2_CS_DRAINING:
-    return NGTCP2_ERR_INVALID_STATE;
-  default:
-    break;
-  }
-
-  if (path) {
-    ngtcp2_path_copy(path, &conn->dcid.current.ps.path);
-  }
 
   fr.type = NGTCP2_FRAME_CONNECTION_CLOSE;
   fr.connection_close.error_code = error_code;
@@ -9001,18 +8980,6 @@ ngtcp2_ssize ngtcp2_conn_write_connection_close(ngtcp2_conn *conn,
   fr.connection_close.reasonlen = 0;
   fr.connection_close.reason = NULL;
 
-  if (conn->state == NGTCP2_CS_POST_HANDSHAKE) {
-    pkt_type = NGTCP2_PKT_SHORT;
-  } else if (hs_pktns && hs_pktns->crypto.tx.ckm) {
-    pkt_type = NGTCP2_PKT_HANDSHAKE;
-  } else if (in_pktns && in_pktns->crypto.tx.ckm) {
-    pkt_type = NGTCP2_PKT_INITIAL;
-  } else {
-    /* This branch is taken if server has not read any Initial packet
-       from client. */
-    return NGTCP2_ERR_INVALID_STATE;
-  }
-
   if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) &&
       pkt_type != NGTCP2_PKT_INITIAL) {
     if (in_pktns && conn->server) {
@@ -9057,9 +9024,61 @@ ngtcp2_ssize ngtcp2_conn_write_connection_close(ngtcp2_conn *conn,
     return NGTCP2_ERR_NOBUF;
   }
 
+  return res;
+}
+
+ngtcp2_ssize ngtcp2_conn_write_connection_close(ngtcp2_conn *conn,
+                                                ngtcp2_path *path,
+                                                uint8_t *dest, size_t destlen,
+                                                uint64_t error_code,
+                                                ngtcp2_tstamp ts) {
+  ngtcp2_pktns *in_pktns = conn->in_pktns;
+  ngtcp2_pktns *hs_pktns = conn->hs_pktns;
+  uint8_t pkt_type;
+  ngtcp2_ssize nwrite;
+
+  conn->log.last_ts = ts;
+  conn->qlog.last_ts = ts;
+
+  if (conn_check_pkt_num_exhausted(conn)) {
+    return NGTCP2_ERR_PKT_NUM_EXHAUSTED;
+  }
+
+  switch (conn->state) {
+  case NGTCP2_CS_CLIENT_INITIAL:
+  case NGTCP2_CS_CLOSING:
+  case NGTCP2_CS_DRAINING:
+    return NGTCP2_ERR_INVALID_STATE;
+  default:
+    break;
+  }
+
+  if (path) {
+    ngtcp2_path_copy(path, &conn->dcid.current.ps.path);
+  }
+
+  if (conn->state == NGTCP2_CS_POST_HANDSHAKE ||
+      (conn->server && conn->pktns.crypto.tx.ckm)) {
+    pkt_type = NGTCP2_PKT_SHORT;
+  } else if (hs_pktns && hs_pktns->crypto.tx.ckm) {
+    pkt_type = NGTCP2_PKT_HANDSHAKE;
+  } else if (in_pktns && in_pktns->crypto.tx.ckm) {
+    pkt_type = NGTCP2_PKT_INITIAL;
+  } else {
+    /* This branch is taken if server has not read any Initial packet
+       from client. */
+    return NGTCP2_ERR_INVALID_STATE;
+  }
+
+  nwrite = conn_write_connection_close(conn, dest, destlen, pkt_type,
+                                       error_code, ts);
+  if (nwrite < 0) {
+    return nwrite;
+  }
+
   conn->state = NGTCP2_CS_CLOSING;
 
-  return res;
+  return nwrite;
 }
 
 ngtcp2_ssize ngtcp2_conn_write_application_close(ngtcp2_conn *conn,
@@ -9068,13 +9087,9 @@ ngtcp2_ssize ngtcp2_conn_write_application_close(ngtcp2_conn *conn,
                                                  uint64_t app_error_code,
                                                  ngtcp2_tstamp ts) {
   ngtcp2_ssize nwrite;
+  ngtcp2_ssize res = 0;
   ngtcp2_frame fr;
 
-  if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)) {
-    return ngtcp2_conn_write_connection_close(conn, path, dest, destlen,
-                                              NGTCP2_APPLICATION_ERROR, ts);
-  }
-
   conn->log.last_ts = ts;
   conn->qlog.last_ts = ts;
 
@@ -9083,12 +9098,38 @@ ngtcp2_ssize ngtcp2_conn_write_application_close(ngtcp2_conn *conn,
   }
 
   switch (conn->state) {
-  case NGTCP2_CS_POST_HANDSHAKE:
-    break;
-  default:
+  case NGTCP2_CS_CLIENT_INITIAL:
+  case NGTCP2_CS_CLOSING:
+  case NGTCP2_CS_DRAINING:
     return NGTCP2_ERR_INVALID_STATE;
+  default:
+    break;
+  }
+
+  if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)) {
+    nwrite = conn_write_connection_close(conn, dest, destlen,
+                                         conn->hs_pktns->crypto.tx.ckm
+                                             ? NGTCP2_PKT_HANDSHAKE
+                                             : NGTCP2_PKT_INITIAL,
+                                         NGTCP2_APPLICATION_ERROR, ts);
+    if (nwrite < 0) {
+      return nwrite;
+    }
+    res = nwrite;
+    dest += nwrite;
+    destlen -= (size_t)nwrite;
+  }
+
+  if (conn->state != NGTCP2_CS_POST_HANDSHAKE) {
+    assert(res);
+
+    if (!conn->server || !conn->pktns.crypto.tx.ckm) {
+      return res;
+    }
   }
 
+  assert(conn->pktns.crypto.tx.ckm);
+
   if (path) {
     ngtcp2_path_copy(path, &conn->dcid.current.ps.path);
   }
@@ -9107,13 +9148,15 @@ ngtcp2_ssize ngtcp2_conn_write_application_close(ngtcp2_conn *conn,
     return nwrite;
   }
 
-  if (nwrite == 0) {
+  res += (size_t)nwrite;
+
+  if (res == 0) {
     return NGTCP2_ERR_NOBUF;
   }
 
   conn->state = NGTCP2_CS_CLOSING;
 
-  return nwrite;
+  return res;
 }
 
 int ngtcp2_conn_is_in_closing_period(ngtcp2_conn *conn) {
diff --git a/tests/main.c b/tests/main.c
index 5290f638be871926b5b9b7b705ecc139be05e456..b6c46b8da8c3b14c77c938c4e03058ca549be99a 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -260,6 +260,8 @@ int main() {
                    test_ngtcp2_conn_set_remote_transport_params) ||
       !CU_add_test(pSuite, "conn_write_connection_close",
                    test_ngtcp2_conn_write_connection_close) ||
+      !CU_add_test(pSuite, "conn_write_application_close",
+                   test_ngtcp2_conn_write_application_close) ||
       !CU_add_test(pSuite, "conn_rtb_reclaim_on_pto",
                    test_ngtcp2_conn_rtb_reclaim_on_pto) ||
       !CU_add_test(pSuite, "pkt_write_connection_close",
diff --git a/tests/ngtcp2_conn_test.c b/tests/ngtcp2_conn_test.c
index d1b313d1ec9f067a78d361327192e36a5548ecfb..33f50b34346cc599d6e4ce3a96571f3929891359 100644
--- a/tests/ngtcp2_conn_test.c
+++ b/tests/ngtcp2_conn_test.c
@@ -5629,6 +5629,189 @@ void test_ngtcp2_conn_write_connection_close(void) {
   ngtcp2_conn_del(conn);
 }
 
+void test_ngtcp2_conn_write_application_close(void) {
+  ngtcp2_conn *conn;
+  uint8_t buf[1200];
+  ngtcp2_ssize spktlen, shdlen;
+  ngtcp2_pkt_hd hd;
+  const uint8_t *p;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
+  uint64_t app_err_code = 0;
+
+  /* Client only Initial key */
+  setup_handshake_client(&conn);
+
+  spktlen = ngtcp2_conn_write_pkt(conn, NULL, buf, sizeof(buf), 0);
+
+  CU_ASSERT(spktlen > 0);
+
+  spktlen = ngtcp2_conn_write_application_close(conn, NULL, buf, sizeof(buf),
+                                                app_err_code, 0);
+
+  CU_ASSERT(spktlen > 0);
+
+  shdlen = ngtcp2_pkt_decode_hd_long(&hd, buf, (size_t)spktlen);
+
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(NGTCP2_PKT_INITIAL == hd.type);
+  CU_ASSERT(shdlen + (ngtcp2_ssize)hd.len == spktlen);
+
+  ngtcp2_conn_del(conn);
+
+  /* Client has Initial and Handshake keys */
+  setup_handshake_client(&conn);
+
+  spktlen = ngtcp2_conn_write_pkt(conn, NULL, buf, sizeof(buf), 0);
+
+  CU_ASSERT(spktlen > 0);
+
+  ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_set_aead_overhead(conn, NGTCP2_FAKE_AEAD_OVERHEAD);
+
+  spktlen = ngtcp2_conn_write_application_close(conn, NULL, buf, sizeof(buf),
+                                                app_err_code, 0);
+
+  CU_ASSERT(spktlen > 0);
+
+  shdlen = ngtcp2_pkt_decode_hd_long(&hd, buf, (size_t)spktlen);
+
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type);
+  CU_ASSERT(shdlen + (ngtcp2_ssize)hd.len == spktlen);
+
+  ngtcp2_conn_del(conn);
+
+  /* Client has all keys and has not confirmed handshake */
+  setup_handshake_client(&conn);
+
+  ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), &aead_ctx,
+                             null_iv, sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_set_aead_overhead(conn, NGTCP2_FAKE_AEAD_OVERHEAD);
+
+  conn->state = NGTCP2_CS_POST_HANDSHAKE;
+
+  spktlen = ngtcp2_conn_write_application_close(conn, NULL, buf, sizeof(buf),
+                                                app_err_code, 0);
+
+  CU_ASSERT(spktlen > 0);
+
+  p = buf;
+
+  shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen);
+
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type);
+
+  p += shdlen + (ngtcp2_ssize)hd.len;
+  spktlen -= shdlen + (ngtcp2_ssize)hd.len;
+
+  shdlen = ngtcp2_pkt_decode_hd_short(&hd, p, (size_t)spktlen,
+                                      conn->dcid.current.cid.datalen);
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(NGTCP2_PKT_SHORT == hd.type);
+
+  ngtcp2_conn_del(conn);
+
+  /* Client has confirmed handshake */
+  setup_default_client(&conn);
+
+  spktlen = ngtcp2_conn_write_application_close(conn, NULL, buf, sizeof(buf),
+                                                app_err_code, 0);
+
+  CU_ASSERT(spktlen > 0);
+
+  shdlen = ngtcp2_pkt_decode_hd_short(&hd, buf, (size_t)spktlen,
+                                      conn->dcid.current.cid.datalen);
+
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(NGTCP2_PKT_SHORT == hd.type);
+
+  ngtcp2_conn_del(conn);
+
+  /* Server has Initial and Handshake key */
+  setup_handshake_server(&conn);
+
+  spktlen = ngtcp2_conn_write_application_close(conn, NULL, buf, sizeof(buf),
+                                                app_err_code, 0);
+
+  CU_ASSERT(spktlen > 0);
+
+  p = buf;
+
+  shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen);
+
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(NGTCP2_PKT_INITIAL == hd.type);
+
+  p += shdlen + (ngtcp2_ssize)hd.len;
+  spktlen -= shdlen + (ngtcp2_ssize)hd.len;
+
+  shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen);
+
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type);
+  CU_ASSERT(shdlen + (ngtcp2_ssize)hd.len == spktlen);
+
+  ngtcp2_conn_del(conn);
+
+  /* Server has all keys and has not confirmed handshake */
+  setup_handshake_server(&conn);
+
+  ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), &aead_ctx,
+                             null_iv, sizeof(null_iv), &hp_ctx);
+
+  spktlen = ngtcp2_conn_write_application_close(conn, NULL, buf, sizeof(buf),
+                                                app_err_code, 0);
+
+  CU_ASSERT(spktlen > 0);
+
+  p = buf;
+
+  shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen);
+
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(NGTCP2_PKT_INITIAL == hd.type);
+
+  p += shdlen + (ngtcp2_ssize)hd.len;
+  spktlen -= shdlen + (ngtcp2_ssize)hd.len;
+
+  shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen);
+
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type);
+
+  p += shdlen + (ngtcp2_ssize)hd.len;
+  spktlen -= shdlen + (ngtcp2_ssize)hd.len;
+
+  shdlen = ngtcp2_pkt_decode_hd_short(&hd, p, (size_t)spktlen,
+                                      conn->dcid.current.cid.datalen);
+
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(NGTCP2_PKT_SHORT == hd.type);
+
+  ngtcp2_conn_del(conn);
+
+  /* Server has confirmed handshake */
+  setup_default_server(&conn);
+
+  spktlen = ngtcp2_conn_write_application_close(conn, NULL, buf, sizeof(buf),
+                                                app_err_code, 0);
+
+  CU_ASSERT(spktlen > 0);
+
+  shdlen = ngtcp2_pkt_decode_hd_short(&hd, buf, (size_t)spktlen,
+                                      conn->dcid.current.cid.datalen);
+
+  CU_ASSERT(shdlen > 0);
+  CU_ASSERT(NGTCP2_PKT_SHORT == hd.type);
+
+  ngtcp2_conn_del(conn);
+}
+
 void test_ngtcp2_conn_rtb_reclaim_on_pto(void) {
   ngtcp2_conn *conn;
   int rv;
diff --git a/tests/ngtcp2_conn_test.h b/tests/ngtcp2_conn_test.h
index 8c8f6d7fda5494216d6449598625c2f70d99d316..5be0d3acac0a52f03401d14bae6677ab4855b6d2 100644
--- a/tests/ngtcp2_conn_test.h
+++ b/tests/ngtcp2_conn_test.h
@@ -75,6 +75,7 @@ void test_ngtcp2_conn_recv_version_negotiation(void);
 void test_ngtcp2_conn_send_initial_token(void);
 void test_ngtcp2_conn_set_remote_transport_params(void);
 void test_ngtcp2_conn_write_connection_close(void);
+void test_ngtcp2_conn_write_application_close(void);
 void test_ngtcp2_conn_rtb_reclaim_on_pto(void);
 void test_ngtcp2_pkt_write_connection_close(void);