diff --git a/common/djangoapps/newrelic_custom_metrics/middleware.py b/common/djangoapps/newrelic_custom_metrics/middleware.py
index 3af8cb5..3d6bcd8 100644
--- a/common/djangoapps/newrelic_custom_metrics/middleware.py
+++ b/common/djangoapps/newrelic_custom_metrics/middleware.py
@@ -6,7 +6,14 @@ This middleware will only call on the newrelic agent if there are any metrics
 to report for this request, so it will not incur any processing overhead for
 request handlers which do not record custom metrics.
 """
-import newrelic.agent
+import logging
+log = logging.getLogger(__name__)
+try:
+    import newrelic.agent
+except ImportError:
+    log.warning("Unable to load NewRelic agent module")
+    newrelic = None  # pylint: disable=invalid-name
+
 import request_cache
 
 REQUEST_CACHE_KEY = 'newrelic_custom_metrics'
@@ -40,6 +47,8 @@ class NewRelicCustomMetrics(object):
         """
         Report the collected custom metrics to New Relic.
         """
+        if not newrelic:
+            return
         metrics_cache = cls._get_metrics_cache()
         for metric_name, metric_value in metrics_cache.iteritems():
             newrelic.agent.add_custom_parameter(metric_name, metric_value)
diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py
index 8233f2a..ecda5cc 100644
--- a/common/lib/xmodule/xmodule/seq_module.py
+++ b/common/lib/xmodule/xmodule/seq_module.py
@@ -14,7 +14,6 @@ from lxml import etree
 from xblock.core import XBlock
 from xblock.fields import Integer, Scope, Boolean, String
 from xblock.fragment import Fragment
-import newrelic.agent
 
 from .exceptions import NotFoundError
 from .fields import Date
@@ -25,6 +24,11 @@ from .xml_module import XmlDescriptor
 
 log = logging.getLogger(__name__)
 
+try:
+    import newrelic.agent
+except ImportError:
+    newrelic = None  # pylint: disable=invalid-name
+
 # HACK: This shouldn't be hard-coded to two types
 # OBSOLETE: This obsoletes 'type'
 class_priority = ['video', 'problem']
@@ -385,6 +389,8 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
         """
         Capture basic information about this sequence in New Relic.
         """
+        if not newrelic:
+            return
         newrelic.agent.add_custom_parameter('seq.block_id', unicode(self.location))
         newrelic.agent.add_custom_parameter('seq.display_name', self.display_name or '')
         newrelic.agent.add_custom_parameter('seq.position', self.position)
@@ -396,6 +402,8 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
         the sequence as a whole. We send this information to New Relic so that
         we can do better performance analysis of courseware.
         """
+        if not newrelic:
+            return
         # Basic count of the number of Units (a.k.a. VerticalBlocks) we have in
         # this learning sequence
         newrelic.agent.add_custom_parameter('seq.num_units', len(display_items))
@@ -414,6 +422,8 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
         """
         Capture information about the current selected Unit within the Sequence.
         """
+        if not newrelic:
+            return
         # Positions are stored with indexing starting at 1. If we get into a
         # weird state where the saved position is out of bounds (e.g. the
         # content was changed), avoid going into any details about this unit.
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 0cef0cb..beb4dd0 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -8,7 +8,6 @@ import logging
 from collections import OrderedDict
 from functools import partial
 
-import newrelic.agent
 from capa.xqueue_interface import XQueueInterface
 from django.conf import settings
 from django.contrib.auth.models import User
@@ -82,6 +81,10 @@ from .field_overrides import OverrideFieldData
 
 log = logging.getLogger(__name__)
 
+try:
+    import newrelic.agent
+except ImportError:
+    newrelic = None  # pylint: disable=invalid-name
 
 if settings.XQUEUE_INTERFACE.get('basic_auth') is not None:
     REQUESTS_AUTH = HTTPBasicAuth(*settings.XQUEUE_INTERFACE['basic_auth'])
@@ -966,9 +969,10 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, course
     except InvalidKeyError:
         raise Http404
 
-    # Gather metrics for New Relic so we can slice data in New Relic Insights
-    newrelic.agent.add_custom_parameter('course_id', unicode(course_key))
-    newrelic.agent.add_custom_parameter('org', unicode(course_key.org))
+    if newrelic:
+        # Gather metrics for New Relic so we can slice data in New Relic Insights
+        newrelic.agent.add_custom_parameter('course_id', unicode(course_key))
+        newrelic.agent.add_custom_parameter('org', unicode(course_key.org))
 
     with modulestore().bulk_operations(course_key):
         instance, tracking_context = get_module_by_usage_id(request, course_id, usage_id, course=course)
@@ -978,7 +982,8 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, course
         # "handler" in those cases is always just "xmodule_handler".
         nr_tx_name = "{}.{}".format(instance.__class__.__name__, handler)
         nr_tx_name += "/{}".format(suffix) if (suffix and handler == "xmodule_handler") else ""
-        newrelic.agent.set_transaction_name(nr_tx_name, group="Python/XBlock/Handler")
+        if newrelic:
+            newrelic.agent.set_transaction_name(nr_tx_name, group="Python/XBlock/Handler")
 
         tracking_context_name = 'module_callback_handler'
         req = django_to_webob_request(request)
diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py
index e07f055..3a93d4e 100644
--- a/lms/djangoapps/courseware/views/index.py
+++ b/lms/djangoapps/courseware/views/index.py
@@ -19,7 +19,14 @@ from django.shortcuts import redirect
 from courseware.url_helpers import get_redirect_url_for_global_staff
 from edxmako.shortcuts import render_to_response, render_to_string
 import logging
-import newrelic.agent
+
+log = logging.getLogger("edx.courseware.views.index")
+
+try:
+    import newrelic.agent
+except ImportError:
+    newrelic = None  # pylint: disable=invalid-name
+
 import urllib
 
 from xblock.fragment import Fragment
@@ -54,7 +61,6 @@ from ..module_render import toc_for_course, get_module_for_descriptor
 from .views import get_current_child, registered_for_course
 
 
-log = logging.getLogger("edx.courseware.views.index")
 TEMPLATE_IMPORTS = {'urllib': urllib}
 CONTENT_DEPTH = 2
 
@@ -174,6 +180,8 @@ class CoursewareIndex(View):
         """
         Initialize metrics for New Relic so we can slice data in New Relic Insights
         """
+        if not newrelic:
+            return
         newrelic.agent.add_custom_parameter('course_id', unicode(self.course_key))
         newrelic.agent.add_custom_parameter('org', unicode(self.course_key.org))
 
diff --git a/lms/djangoapps/discussion/views.py b/lms/djangoapps/discussion/views.py
index db5df8d..6822503 100644
--- a/lms/djangoapps/discussion/views.py
+++ b/lms/djangoapps/discussion/views.py
@@ -17,7 +17,13 @@ from django.shortcuts import render_to_response
 from django.template.loader import render_to_string
 from django.utils.translation import get_language_bidi
 from django.views.decorators.http import require_GET
-import newrelic.agent
+
+log = logging.getLogger("edx.discussions")
+try:
+    import newrelic.agent
+except ImportError:
+    newrelic = None  # pylint: disable=invalid-name
+
 from rest_framework import status
 
 from web_fragments.fragment import Fragment
@@ -50,13 +56,27 @@ import lms.lib.comment_client as cc
 
 from opaque_keys.edx.keys import CourseKey
 
+from contextlib import contextmanager
+
 THREADS_PER_PAGE = 20
 INLINE_THREADS_PER_PAGE = 20
 PAGES_NEARBY_DELTA = 2
-log = logging.getLogger("edx.discussions")
 
 
-@newrelic.agent.function_trace()
+@contextmanager
+def newrelic_function_trace(function_name):
+    """
+    A wrapper context manager newrelic.agent.FunctionTrace to no-op if the
+    newrelic package is not installed
+    """
+    if newrelic:
+        nr_transaction = newrelic.agent.current_transaction()
+        with newrelic.agent.FunctionTrace(nr_transaction, function_name):
+            yield
+    else:
+        yield
+
+
 def make_course_settings(course, user):
     """
     Generate a JSON-serializable model for course settings, which will be used to initialize a
@@ -71,7 +91,6 @@ def make_course_settings(course, user):
     }
 
 
-@newrelic.agent.function_trace()
 def get_threads(request, course, user_info, discussion_id=None, per_page=THREADS_PER_PAGE):
     """
     This may raise an appropriate subclass of cc.utils.CommentClientError
@@ -184,7 +203,6 @@ def inline_discussion(request, course_key, discussion_id):
     """
     Renders JSON for DiscussionModules
     """
-    nr_transaction = newrelic.agent.current_transaction()
 
     course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
     cc_user = cc.User.from_django_user(request.user)
@@ -195,12 +213,14 @@ def inline_discussion(request, course_key, discussion_id):
     except ValueError:
         return HttpResponseServerError("Invalid group_id")
 
-    with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"):
+    with newrelic_function_trace("get_metadata_for_threads"):
         annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info)
+
     is_staff = has_permission(request.user, 'openclose_thread', course.id)
     threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads]
-    with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"):
+    with newrelic_function_trace("add_courseware_context"):
         add_courseware_context(threads, course, request.user)
+
     return utils.JsonResponse({
         'is_commentable_cohorted': is_commentable_cohorted(course_key, discussion_id),
         'discussion_data': threads,
@@ -219,8 +239,6 @@ def forum_form_discussion(request, course_key):
     """
     Renders the main Discussion page, potentially filtered by a search query
     """
-    nr_transaction = newrelic.agent.current_transaction()
-
     course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
     if request.is_ajax():
         user = cc.User.from_django_user(request.user)
@@ -235,10 +253,10 @@ def forum_form_discussion(request, course_key):
         except ValueError:
             return HttpResponseServerError("Invalid group_id")
 
-        with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"):
+        with newrelic_function_trace("get_metadata_for_threads"):
             annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info)
 
-        with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"):
+        with newrelic_function_trace("add_courseware_context"):
             add_courseware_context(threads, course, request.user)
 
         return utils.JsonResponse({
@@ -265,8 +283,6 @@ def single_thread(request, course_key, discussion_id, thread_id):
 
     Depending on the HTTP headers, we'll adjust our response accordingly.
     """
-    nr_transaction = newrelic.agent.current_transaction()
-
     course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
 
     if request.is_ajax():
@@ -278,7 +294,7 @@ def single_thread(request, course_key, discussion_id, thread_id):
         if not thread:
             raise Http404
 
-        with newrelic.agent.FunctionTrace(nr_transaction, "get_annotated_content_infos"):
+        with newrelic_function_trace("get_annotated_content_infos"):
             annotated_content_info = utils.get_annotated_content_infos(
                 course_key,
                 thread,
@@ -287,7 +303,7 @@ def single_thread(request, course_key, discussion_id, thread_id):
             )
 
         content = utils.prepare_content(thread.to_dict(), course_key, is_staff)
-        with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"):
+        with newrelic_function_trace("add_courseware_context"):
             add_courseware_context([content], course, request.user)
 
         return utils.JsonResponse({
@@ -372,7 +388,6 @@ def _create_discussion_board_context(request, course_key, discussion_id=None, th
     """
     Returns the template context for rendering the discussion board.
     """
-    nr_transaction = newrelic.agent.current_transaction()
     context = _create_base_discussion_view_context(request, course_key)
     course = context['course']
     course_settings = context['course_settings']
@@ -401,13 +416,13 @@ def _create_discussion_board_context(request, course_key, discussion_id=None, th
     is_staff = has_permission(user, 'openclose_thread', course.id)
     threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads]
 
-    with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"):
+    with newrelic_function_trace("get_metadata_for_threads"):
         annotated_content_info = utils.get_metadata_for_threads(course_key, threads, user, user_info)
 
-    with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"):
+    with newrelic_function_trace("add_courseware_context"):
         add_courseware_context(threads, course, user)
 
-    with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"):
+    with newrelic_function_trace("get_cohort_info"):
         user_cohort_id = get_cohort_id(user, course_key)
 
     context.update({
@@ -435,9 +450,6 @@ def user_profile(request, course_key, user_id):
     Renders a response to display the user profile page (shown after clicking
     on a post author's username).
     """
-
-    nr_transaction = newrelic.agent.current_transaction()
-
     user = cc.User.from_django_user(request.user)
     course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
 
@@ -466,13 +478,13 @@ def user_profile(request, course_key, user_id):
         query_params['page'] = page
         query_params['num_pages'] = num_pages
 
-        with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"):
+        with newrelic_function_trace("get_metadata_for_threads"):
             user_info = cc.User.from_django_user(request.user).to_dict()
             annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info)
 
         is_staff = has_permission(request.user, 'openclose_thread', course.id)
         threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads]
-        with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"):
+        with newrelic_function_trace("add_courseware_context"):
             add_courseware_context(threads, course, request.user)
         if request.is_ajax():
             return utils.JsonResponse({
@@ -486,7 +498,7 @@ def user_profile(request, course_key, user_id):
                 course_id=course.id
             ).order_by("name").values_list("name", flat=True).distinct()
 
-            with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"):
+            with newrelic_function_trace("get_cohort_info"):
                 user_cohort_id = get_cohort_id(request.user, course_key)
 
             context = _create_base_discussion_view_context(request, course_key)
@@ -514,9 +526,6 @@ def followed_threads(request, course_key, user_id):
     """
     Ajax-only endpoint retrieving the threads followed by a specific user.
     """
-
-    nr_transaction = newrelic.agent.current_transaction()
-
     course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
     try:
         profiled_user = cc.User(id=user_id, course_id=course_key)
@@ -557,7 +566,7 @@ def followed_threads(request, course_key, user_id):
         query_params['num_pages'] = paginated_results.num_pages
         user_info = cc.User.from_django_user(request.user).to_dict()
 
-        with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"):
+        with newrelic_function_trace("get_metadata_for_threads"):
             annotated_content_info = utils.get_metadata_for_threads(
                 course_key,
                 paginated_results.collection,
diff --git a/lms/djangoapps/grades/tasks.py b/lms/djangoapps/grades/tasks.py
index 0359dee..b4c51fd 100644
--- a/lms/djangoapps/grades/tasks.py
+++ b/lms/djangoapps/grades/tasks.py
@@ -9,7 +9,12 @@ from django.contrib.auth.models import User
 from django.core.exceptions import ValidationError
 from django.db.utils import DatabaseError
 from logging import getLogger
-import newrelic.agent
+
+log = getLogger(__name__)
+try:
+    import newrelic.agent
+except ImportError:
+    newrelic = None  # pylint: disable=invalid-name
 
 from celery_utils.logged_task import LoggedTask
 from celery_utils.persist_on_failure import PersistOnFailureTask
@@ -31,8 +36,6 @@ from .new.subsection_grade import SubsectionGradeFactory
 from .signals.signals import SUBSECTION_SCORE_CHANGED
 from .transformer import GradesTransformer
 
-log = getLogger(__name__)
-
 
 class DatabaseNotReadyError(IOError):
     """
@@ -96,8 +99,9 @@ def _recalculate_subsection_grade(self, **kwargs):
 
         scored_block_usage_key = UsageKey.from_string(kwargs['usage_id']).replace(course_key=course_key)
 
-        newrelic.agent.add_custom_parameter('course_id', unicode(course_key))
-        newrelic.agent.add_custom_parameter('usage_id', unicode(scored_block_usage_key))
+        if newrelic:
+            newrelic.agent.add_custom_parameter('course_id', unicode(course_key))
+            newrelic.agent.add_custom_parameter('usage_id', unicode(scored_block_usage_key))
 
         # The request cache is not maintained on celery workers,
         # where this code runs. So we take the values from the
diff --git a/openedx/core/djangoapps/contentserver/middleware.py b/openedx/core/djangoapps/contentserver/middleware.py
index 9fb41c0..8f6fe3e 100644
--- a/openedx/core/djangoapps/contentserver/middleware.py
+++ b/openedx/core/djangoapps/contentserver/middleware.py
@@ -4,7 +4,11 @@ Middleware to serve assets.
 
 import logging
 import datetime
-import newrelic.agent
+log = logging.getLogger(__name__)
+try:
+    import newrelic.agent
+except ImportError:
+    newrelic = None  # pylint: disable=invalid-name
 from django.http import (
     HttpResponse, HttpResponseNotModified, HttpResponseForbidden,
     HttpResponseBadRequest, HttpResponseNotFound, HttpResponsePermanentRedirect)
@@ -25,7 +29,6 @@ from .models import CourseAssetCacheTtlConfig, CdnUserAgentsConfig
 # TODO: Soon as we have a reasonable way to serialize/deserialize AssetKeys, we need
 # to change this file so instead of using course_id_partial, we're just using asset keys
 
-log = logging.getLogger(__name__)
 HTTP_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
 
 
@@ -86,17 +89,18 @@ class StaticContentServer(object):
             if safe_course_key.run is None:
                 safe_course_key = safe_course_key.replace(run='only')
 
-            newrelic.agent.add_custom_parameter('course_id', safe_course_key)
-            newrelic.agent.add_custom_parameter('org', loc.org)
-            newrelic.agent.add_custom_parameter('contentserver.path', loc.path)
+            if newrelic:
+                newrelic.agent.add_custom_parameter('course_id', safe_course_key)
+                newrelic.agent.add_custom_parameter('org', loc.org)
+                newrelic.agent.add_custom_parameter('contentserver.path', loc.path)
 
-            # Figure out if this is a CDN using us as the origin.
-            is_from_cdn = StaticContentServer.is_cdn_request(request)
-            newrelic.agent.add_custom_parameter('contentserver.from_cdn', is_from_cdn)
+                # Figure out if this is a CDN using us as the origin.
+                is_from_cdn = StaticContentServer.is_cdn_request(request)
+                newrelic.agent.add_custom_parameter('contentserver.from_cdn', is_from_cdn)
 
-            # Check if this content is locked or not.
-            locked = self.is_content_locked(content)
-            newrelic.agent.add_custom_parameter('contentserver.locked', locked)
+                # Check if this content is locked or not.
+                locked = self.is_content_locked(content)
+                newrelic.agent.add_custom_parameter('contentserver.locked', locked)
 
             # Check that user has access to the content.
             if not self.is_user_authorized(request, content, loc):
@@ -153,7 +157,8 @@ class StaticContentServer(object):
                             response['Content-Length'] = str(last - first + 1)
                             response.status_code = 206  # Partial Content
 
-                            newrelic.agent.add_custom_parameter('contentserver.ranged', True)
+                            if newrelic:
+                                newrelic.agent.add_custom_parameter('contentserver.ranged', True)
                         else:
                             log.warning(
                                 u"Cannot satisfy ranges in Range header: %s for content: %s", header_value, unicode(loc)
@@ -165,8 +170,9 @@ class StaticContentServer(object):
                 response = HttpResponse(content.stream_data())
                 response['Content-Length'] = content.length
 
-            newrelic.agent.add_custom_parameter('contentserver.content_len', content.length)
-            newrelic.agent.add_custom_parameter('contentserver.content_type', content.content_type)
+            if newrelic:
+                newrelic.agent.add_custom_parameter('contentserver.content_len', content.length)
+                newrelic.agent.add_custom_parameter('contentserver.content_type', content.content_type)
 
             # "Accept-Ranges: bytes" tells the user that only "bytes" ranges are allowed
             response['Accept-Ranges'] = 'bytes'
@@ -194,12 +200,14 @@ class StaticContentServer(object):
         # indicate there should be no caching whatsoever.
         cache_ttl = CourseAssetCacheTtlConfig.get_cache_ttl()
         if cache_ttl > 0 and not is_locked:
-            newrelic.agent.add_custom_parameter('contentserver.cacheable', True)
+            if newrelic:
+                newrelic.agent.add_custom_parameter('contentserver.cacheable', True)
 
             response['Expires'] = StaticContentServer.get_expiration_value(datetime.datetime.utcnow(), cache_ttl)
             response['Cache-Control'] = "public, max-age={ttl}, s-maxage={ttl}".format(ttl=cache_ttl)
         elif is_locked:
-            newrelic.agent.add_custom_parameter('contentserver.cacheable', False)
+            if newrelic:
+                newrelic.agent.add_custom_parameter('contentserver.cacheable', False)
 
             response['Cache-Control'] = "private, no-cache, no-store"