diff --git a/config.yaml b/config.yaml
index 8157f7c9..05d366cd 100644
--- a/config.yaml
+++ b/config.yaml
@@ -235,6 +235,18 @@ options:
       192.168.0.0/24)
       .
       This network will be used for public endpoints.
+  os-public-hostname:
+    type: string
+    default:
+    description: |
+      The hostname or address of the public endpoints created for neutron-api
+      in the keystone identity provider.
+      .
+      This value will be used for public endpoints. For example, an
+      os-public-hostname set to 'neutron-api.example.com' with ssl enabled
+      will create the following endpoint for neutron-api:
+      .
+      https://neutron-api.example.com:9696/
   ssl_cert:
     type: string
     default:
diff --git a/hooks/charmhelpers/contrib/openstack/ip.py b/hooks/charmhelpers/contrib/openstack/ip.py
index 29bbddcb..3dca6dc1 100644
--- a/hooks/charmhelpers/contrib/openstack/ip.py
+++ b/hooks/charmhelpers/contrib/openstack/ip.py
@@ -17,6 +17,7 @@
 from charmhelpers.core.hookenv import (
     config,
     unit_get,
+    service_name,
 )
 from charmhelpers.contrib.network.ip import (
     get_address_in_network,
@@ -26,8 +27,6 @@ from charmhelpers.contrib.network.ip import (
 )
 from charmhelpers.contrib.hahelpers.cluster import is_clustered
 
-from functools import partial
-
 PUBLIC = 'public'
 INTERNAL = 'int'
 ADMIN = 'admin'
@@ -35,15 +34,18 @@ ADMIN = 'admin'
 ADDRESS_MAP = {
     PUBLIC: {
         'config': 'os-public-network',
-        'fallback': 'public-address'
+        'fallback': 'public-address',
+        'override': 'os-public-hostname',
     },
     INTERNAL: {
         'config': 'os-internal-network',
-        'fallback': 'private-address'
+        'fallback': 'private-address',
+        'override': 'os-internal-hostname',
     },
     ADMIN: {
         'config': 'os-admin-network',
-        'fallback': 'private-address'
+        'fallback': 'private-address',
+        'override': 'os-admin-hostname',
     }
 }
 
@@ -57,15 +59,50 @@ def canonical_url(configs, endpoint_type=PUBLIC):
     :param endpoint_type: str endpoint type to resolve.
     :param returns: str base URL for services on the current service unit.
     """
-    scheme = 'http'
-    if 'https' in configs.complete_contexts():
-        scheme = 'https'
+    scheme = _get_scheme(configs)
+
     address = resolve_address(endpoint_type)
     if is_ipv6(address):
         address = "[{}]".format(address)
+
     return '%s://%s' % (scheme, address)
 
 
+def _get_scheme(configs):
+    """Returns the scheme to use for the url (either http or https)
+    depending upon whether https is in the configs value.
+
+    :param configs: OSTemplateRenderer config templating object to inspect
+                    for a complete https context.
+    :returns: either 'http' or 'https' depending on whether https is
+              configured within the configs context.
+    """
+    scheme = 'http'
+    if configs and 'https' in configs.complete_contexts():
+        scheme = 'https'
+    return scheme
+
+
+def _get_address_override(endpoint_type=PUBLIC):
+    """Returns any address overrides that the user has defined based on the
+    endpoint type.
+
+    Note: this function allows for the service name to be inserted into the
+    address if the user specifies {service_name}.somehost.org.
+
+    :param endpoint_type: the type of endpoint to retrieve the override
+                          value for.
+    :returns: any endpoint address or hostname that the user has overridden
+              or None if an override is not present.
+    """
+    override_key = ADDRESS_MAP[endpoint_type]['override']
+    addr_override = config(override_key)
+    if not addr_override:
+        return None
+    else:
+        return addr_override.format(service_name=service_name())
+
+
 def resolve_address(endpoint_type=PUBLIC):
     """Return unit address depending on net config.
 
@@ -77,7 +114,10 @@ def resolve_address(endpoint_type=PUBLIC):
 
     :param endpoint_type: Network endpoing type
     """
-    resolved_address = None
+    resolved_address = _get_address_override(endpoint_type)
+    if resolved_address:
+        return resolved_address
+
     vips = config('vip')
     if vips:
         vips = vips.split()
@@ -109,38 +149,3 @@ def resolve_address(endpoint_type=PUBLIC):
                          "clustered=%s)" % (net_type, clustered))
 
     return resolved_address
-
-
-def endpoint_url(configs, url_template, port, endpoint_type=PUBLIC,
-                 override=None):
-    """Returns the correct endpoint URL to advertise to Keystone.
-
-    This method provides the correct endpoint URL which should be advertised to
-    the keystone charm for endpoint creation. This method allows for the url to
-    be overridden to force a keystone endpoint to have specific URL for any of
-    the defined scopes (admin, internal, public).
-
-    :param configs: OSTemplateRenderer config templating object to inspect
-                    for a complete https context.
-    :param url_template: str format string for creating the url template. Only
-                         two values will be passed - the scheme+hostname
-                        returned by the canonical_url and the port.
-    :param endpoint_type: str endpoint type to resolve.
-    :param override: str the name of the config option which overrides the
-                     endpoint URL defined by the charm itself. None will
-                     disable any overrides (default).
-    """
-    if override:
-        # Return any user-defined overrides for the keystone endpoint URL.
-        user_value = config(override)
-        if user_value:
-            return user_value.strip()
-
-    return url_template % (canonical_url(configs, endpoint_type), port)
-
-
-public_endpoint = partial(endpoint_url, endpoint_type=PUBLIC)
-
-internal_endpoint = partial(endpoint_url, endpoint_type=INTERNAL)
-
-admin_endpoint = partial(endpoint_url, endpoint_type=ADMIN)
diff --git a/unit_tests/test_neutron_api_hooks.py b/unit_tests/test_neutron_api_hooks.py
index 70ee610c..377f1483 100644
--- a/unit_tests/test_neutron_api_hooks.py
+++ b/unit_tests/test_neutron_api_hooks.py
@@ -25,7 +25,6 @@ TO_PATCH = [
     'api_port',
     'apt_update',
     'apt_install',
-    'canonical_url',
     'config',
     'CONFIGS',
     'check_call',
@@ -319,8 +318,9 @@ class NeutronAPIHooksTests(CharmTestCase):
         self._call_hook('amqp-relation-broken')
         self.assertTrue(self.CONFIGS.write_all.called)
 
-    def test_identity_joined(self):
-        self.canonical_url.return_value = 'http://127.0.0.1'
+    @patch.object(hooks, 'canonical_url')
+    def test_identity_joined(self, _canonical_url):
+        _canonical_url.return_value = 'http://127.0.0.1'
         self.api_port.return_value = '9696'
         self.test_config.set('region', 'region1')
         _neutron_url = 'http://127.0.0.1:9696'
@@ -337,6 +337,34 @@ class NeutronAPIHooksTests(CharmTestCase):
             relation_settings=_endpoints
         )
 
+    @patch('charmhelpers.contrib.openstack.ip.service_name',
+           lambda *args: 'neutron-api')
+    @patch('charmhelpers.contrib.openstack.ip.unit_get')
+    @patch('charmhelpers.contrib.openstack.ip.is_clustered')
+    @patch('charmhelpers.contrib.openstack.ip.config')
+    def test_identity_changed_public_name(self, _config, _is_clustered,
+                                          _unit_get):
+        _unit_get.return_value = '127.0.0.1'
+        _is_clustered.return_value = False
+        _config.side_effect = self.test_config.get
+        self.api_port.return_value = '9696'
+        self.test_config.set('region', 'region1')
+        self.test_config.set('os-public-hostname',
+                             'neutron-api.example.com')
+        self._call_hook('identity-service-relation-joined')
+        _neutron_url = 'http://127.0.0.1:9696'
+        _endpoints = {
+            'quantum_service': 'quantum',
+            'quantum_region': 'region1',
+            'quantum_public_url': 'http://neutron-api.example.com:9696',
+            'quantum_admin_url': _neutron_url,
+            'quantum_internal_url': _neutron_url,
+        }
+        self.relation_set.assert_called_with(
+            relation_id=None,
+            relation_settings=_endpoints
+        )
+
     def test_identity_changed_partial_ctxt(self):
         self.CONFIGS.complete_contexts.return_value = []
         _api_rel_joined = self.patch('neutron_api_relation_joined')
@@ -353,12 +381,13 @@ class NeutronAPIHooksTests(CharmTestCase):
         self.assertTrue(self.CONFIGS.write.called_with(NEUTRON_CONF))
         self.assertTrue(_api_rel_joined.called)
 
-    def test_neutron_api_relation_no_id_joined(self):
+    @patch.object(hooks, 'canonical_url')
+    def test_neutron_api_relation_no_id_joined(self, _canonical_url):
         host = 'http://127.0.0.1'
         port = 1234
         _id_rel_joined = self.patch('identity_joined')
         self.relation_ids.side_effect = self._fake_relids
-        self.canonical_url.return_value = host
+        _canonical_url.return_value = host
         self.api_port.return_value = port
         self.is_relation_made = False
         neutron_url = '%s:%s' % (host, port)
@@ -381,10 +410,11 @@ class NeutronAPIHooksTests(CharmTestCase):
             **_relation_data
         )
 
-    def test_neutron_api_relation_joined(self):
+    @patch.object(hooks, 'canonical_url')
+    def test_neutron_api_relation_joined(self, _canonical_url):
         host = 'http://127.0.0.1'
         port = 1234
-        self.canonical_url.return_value = host
+        _canonical_url.return_value = host
         self.api_port.return_value = port
         self.is_relation_made = True
         neutron_url = '%s:%s' % (host, port)