diff --git a/src/core/channel/http_client_filter.c b/src/core/channel/http_client_filter.c
index 949f1fefeb6a4ccf53327a36599048d92f4f2421..98e26aa563ec1cbed2306eaf09a759182d50d9b6 100644
--- a/src/core/channel/http_client_filter.c
+++ b/src/core/channel/http_client_filter.c
@@ -34,11 +34,14 @@
 #include "src/core/channel/http_client_filter.h"
 #include <grpc/support/log.h>
 
-typedef struct call_data {
-  int unused; /* C89 requires at least one struct element */
-} call_data;
+typedef struct call_data { int sent_headers; } call_data;
 
-typedef struct channel_data { grpc_mdelem *te_trailers; } channel_data;
+typedef struct channel_data {
+  grpc_mdelem *te_trailers;
+  grpc_mdelem *method;
+  grpc_mdelem *scheme;
+  grpc_mdelem *content_type;
+} channel_data;
 
 /* used to silence 'variable not used' warnings */
 static void ignore_unused(void *ignored) {}
@@ -58,9 +61,26 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem,
   ignore_unused(calld);
 
   switch (op->type) {
+    case GRPC_SEND_METADATA:
+      if (!calld->sent_headers) {
+        /* Send : prefixed headers, which have to be before any application
+         * layer headers. */
+        calld->sent_headers = 1;
+        grpc_call_element_send_metadata(elem, channeld->method);
+        grpc_call_element_send_metadata(elem, channeld->scheme);
+      }
+      grpc_call_next_op(elem, op);
+      break;
     case GRPC_SEND_START:
-      /* just prior to starting, add a te: trailers header */
+      if (!calld->sent_headers) {
+        /* Send : prefixed headers, if we haven't already */
+        calld->sent_headers = 1;
+        grpc_call_element_send_metadata(elem, channeld->method);
+        grpc_call_element_send_metadata(elem, channeld->scheme);
+      }
+      /* Send non : prefixed headers */
       grpc_call_element_send_metadata(elem, channeld->te_trailers);
+      grpc_call_element_send_metadata(elem, channeld->content_type);
       grpc_call_next_op(elem, op);
       break;
     default:
@@ -97,7 +117,7 @@ static void init_call_elem(grpc_call_element *elem,
   ignore_unused(channeld);
 
   /* initialize members */
-  calld->unused = 0;
+  calld->sent_headers = 0;
 }
 
 /* Destructor for call_data */
@@ -125,6 +145,10 @@ static void init_channel_elem(grpc_channel_element *elem,
 
   /* initialize members */
   channeld->te_trailers = grpc_mdelem_from_strings(mdctx, "te", "trailers");
+  channeld->method = grpc_mdelem_from_strings(mdctx, ":method", "POST");
+  channeld->scheme = grpc_mdelem_from_strings(mdctx, ":scheme", "grpc");
+  channeld->content_type =
+      grpc_mdelem_from_strings(mdctx, "content-type", "application/grpc");
 }
 
 /* Destructor for channel data */
@@ -133,6 +157,9 @@ static void destroy_channel_elem(grpc_channel_element *elem) {
   channel_data *channeld = elem->channel_data;
 
   grpc_mdelem_unref(channeld->te_trailers);
+  grpc_mdelem_unref(channeld->method);
+  grpc_mdelem_unref(channeld->scheme);
+  grpc_mdelem_unref(channeld->content_type);
 }
 
 const grpc_channel_filter grpc_http_client_filter = {
diff --git a/src/core/surface/call.c b/src/core/surface/call.c
index 7e894f1f724aff61c6de6ce5b605dd3a4b4d5f27..9ed617f665cc65173fc92dc675e07fe9d8583221 100644
--- a/src/core/surface/call.c
+++ b/src/core/surface/call.c
@@ -291,11 +291,28 @@ void grpc_call_execute_op(grpc_call *call, grpc_call_op *op) {
   elem->filter->call_op(elem, NULL, op);
 }
 
-grpc_call_error grpc_call_add_metadata(grpc_call *call, grpc_metadata *metadata,
-                                       gpr_uint32 flags) {
+void grpc_call_add_mdelem(grpc_call *call, grpc_mdelem *mdelem,
+                          gpr_uint32 flags) {
   grpc_call_element *elem;
   grpc_call_op op;
 
+  GPR_ASSERT(call->state < CALL_FINISHED);
+
+  op.type = GRPC_SEND_METADATA;
+  op.dir = GRPC_CALL_DOWN;
+  op.flags = flags;
+  op.done_cb = do_nothing;
+  op.user_data = NULL;
+  op.data.metadata = mdelem;
+
+  elem = CALL_ELEM_FROM_CALL(call, 0);
+  elem->filter->call_op(elem, NULL, &op);
+}
+
+grpc_call_error grpc_call_add_metadata(grpc_call *call, grpc_metadata *metadata,
+                                       gpr_uint32 flags) {
+  grpc_mdelem *mdelem;
+
   if (call->is_client) {
     if (call->state >= CALL_STARTED) {
       return GRPC_CALL_ERROR_ALREADY_INVOKED;
@@ -306,18 +323,10 @@ grpc_call_error grpc_call_add_metadata(grpc_call *call, grpc_metadata *metadata,
     }
   }
 
-  op.type = GRPC_SEND_METADATA;
-  op.dir = GRPC_CALL_DOWN;
-  op.flags = flags;
-  op.done_cb = do_nothing;
-  op.user_data = NULL;
-  op.data.metadata = grpc_mdelem_from_string_and_buffer(
+  mdelem = grpc_mdelem_from_string_and_buffer(
       call->metadata_context, metadata->key, (gpr_uint8 *)metadata->value,
       metadata->value_length);
-
-  elem = CALL_ELEM_FROM_CALL(call, 0);
-  elem->filter->call_op(elem, NULL, &op);
-
+  grpc_call_add_mdelem(call, mdelem, flags);
   return GRPC_CALL_OK;
 }
 
diff --git a/src/core/surface/call.h b/src/core/surface/call.h
index 2c785a59fce0dfdcd0f74b8d97b62c1a5092f427..5c2ef3be187041a702b6e31d53394cb35ce01fe6 100644
--- a/src/core/surface/call.h
+++ b/src/core/surface/call.h
@@ -70,4 +70,7 @@ grpc_call *grpc_call_from_top_element(grpc_call_element *surface_element);
 /* Get the metadata buffer. */
 grpc_metadata_buffer *grpc_call_get_metadata_buffer(grpc_call *call);
 
+void grpc_call_add_mdelem(grpc_call *call, grpc_mdelem *mdelem,
+                          gpr_uint32 flags);
+
 #endif  /* __GRPC_INTERNAL_SURFACE_CALL_H__ */
diff --git a/src/core/surface/channel.c b/src/core/surface/channel.c
index fe7a3afcc9dc1f5abd572a7212d706658af8ee02..8ef13675fe37064caac6bf8d756d7d94ce5c8a1c 100644
--- a/src/core/surface/channel.c
+++ b/src/core/surface/channel.c
@@ -47,6 +47,8 @@ struct grpc_channel {
   grpc_mdctx *metadata_context;
   grpc_mdstr *grpc_status_string;
   grpc_mdstr *grpc_message_string;
+  grpc_mdstr *path_string;
+  grpc_mdstr *authority_string;
 };
 
 #define CHANNEL_STACK_FROM_CHANNEL(c) ((grpc_channel_stack *)((c)+1))
@@ -63,6 +65,8 @@ grpc_channel *grpc_channel_create_from_filters(
   channel->metadata_context = mdctx;
   channel->grpc_status_string = grpc_mdstr_from_string(mdctx, "grpc-status");
   channel->grpc_message_string = grpc_mdstr_from_string(mdctx, "grpc-message");
+  channel->path_string = grpc_mdstr_from_string(mdctx, ":path");
+  channel->authority_string = grpc_mdstr_from_string(mdctx, ":authority");
   grpc_channel_stack_init(filters, num_filters, args, channel->metadata_context,
                           CHANNEL_STACK_FROM_CHANNEL(channel));
   return channel;
@@ -74,7 +78,8 @@ grpc_call *grpc_channel_create_call(grpc_channel *channel, const char *method,
                                     const char *host,
                                     gpr_timespec absolute_deadline) {
   grpc_call *call;
-  grpc_metadata md;
+  grpc_mdelem *path_mdelem;
+  grpc_mdelem *authority_mdelem;
 
   if (!channel->is_client) {
     gpr_log(GPR_ERROR, "Cannot create a call on the server.");
@@ -83,18 +88,21 @@ grpc_call *grpc_channel_create_call(grpc_channel *channel, const char *method,
 
   call = grpc_call_create(channel, NULL);
 
-#define ADDMD(k, v)                       \
-  do {                                    \
-    md.key = (k);                         \
-    md.value = (char *)(v);               \
-    md.value_length = strlen((v));        \
-    grpc_call_add_metadata(call, &md, 0); \
-  } while (0)
-  ADDMD(":method", "POST");
-  ADDMD(":scheme", "grpc");
-  ADDMD(":path", method);
-  ADDMD(":authority", host);
-  ADDMD("content-type", "application/grpc");
+  /* Add :path and :authority headers. */
+  /* TODO(klempner): Consider optimizing this by stashing mdelems for common
+     values of method and host. */
+  grpc_mdstr_ref(channel->path_string);
+  path_mdelem = grpc_mdelem_from_metadata_strings(
+      channel->metadata_context, channel->path_string,
+      grpc_mdstr_from_string(channel->metadata_context, method));
+  grpc_call_add_mdelem(call, path_mdelem, 0);
+
+  grpc_mdstr_ref(channel->authority_string);
+  authority_mdelem = grpc_mdelem_from_metadata_strings(
+      channel->metadata_context, channel->authority_string,
+      grpc_mdstr_from_string(channel->metadata_context, host));
+  grpc_call_add_mdelem(call, authority_mdelem, 0);
+
   if (0 != gpr_time_cmp(absolute_deadline, gpr_inf_future)) {
     grpc_call_op op;
     op.type = GRPC_SEND_DEADLINE;
@@ -118,6 +126,8 @@ void grpc_channel_internal_unref(grpc_channel *channel) {
     grpc_channel_stack_destroy(CHANNEL_STACK_FROM_CHANNEL(channel));
     grpc_mdstr_unref(channel->grpc_status_string);
     grpc_mdstr_unref(channel->grpc_message_string);
+    grpc_mdstr_unref(channel->path_string);
+    grpc_mdstr_unref(channel->authority_string);
     grpc_mdctx_orphan(channel->metadata_context);
     gpr_free(channel);
   }