diff --git a/Gemfile b/Gemfile
index 46245ab62d1162f4aeab67e080902e2d0328bbeb..033bbe91296ee90807cef745db23bc6d68e06707 100644
--- a/Gemfile
+++ b/Gemfile
@@ -161,6 +161,9 @@ gem 'connection_pool', '~> 2.0'
 # HipChat integration
 gem 'hipchat', '~> 1.5.0'
 
+# JIRA integration
+gem 'jira-ruby', '~> 0.1.17'
+
 # Flowdock integration
 gem 'gitlab-flowdock-git-hook', '~> 1.0.1'
 
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index 4cb3be410645e4aa3cca8c6b0a15706175adc076..c33d7eecb9f7941ba7f8d29d9436ee1abc652480 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -18,7 +18,7 @@ module ServiceParams
                     :add_pusher, :send_from_committer_email, :disable_diffs,
                     :external_wiki_url, :notify, :color,
                     :server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
-                    :jira_issue_transition_id]
+                    :jira_issue_transition_id, :url, :project_key]
 
   # Parameters to ignore if no value is specified
   FILTER_BLANK_PARAMS = [:password]
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index f81b66fd21971080007043ea5eb2db82c5f9466f..580adcfba991ff36a7fd5ee647af1acdc3a29fe2 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -1,15 +1,35 @@
+# == Schema Information
+#
+# Table name: services
+#
+#  id                    :integer          not null, primary key
+#  type                  :string(255)
+#  title                 :string(255)
+#  project_id            :integer
+#  created_at            :datetime
+#  updated_at            :datetime
+#  active                :boolean          default(FALSE), not null
+#  properties            :text
+#  template              :boolean          default(FALSE)
+#  push_events           :boolean          default(TRUE)
+#  issues_events         :boolean          default(TRUE)
+#  merge_requests_events :boolean          default(TRUE)
+#  tag_push_events       :boolean          default(TRUE)
+#  note_events           :boolean          default(TRUE), not null
+#  build_events          :boolean          default(FALSE), not null
+#
+require 'jira'
+
 class JiraService < IssueTrackerService
   include HTTParty
-  include Gitlab::Routing.url_helpers
+  include Gitlab::Application.routes.url_helpers
 
   DEFAULT_API_VERSION = 2
 
-  prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
-                :title, :description, :project_url, :issues_url, :new_issue_url
-
-  validates :api_url, presence: true, url: true, if: :activated?
+  prop_accessor :username, :password, :url, :project_key,
+                :jira_issue_transition_id, :title, :description
 
-  before_validation :set_api_url, :set_jira_issue_transition_id
+  before_validation :set_jira_issue_transition_id
 
   before_update :reset_password
 
@@ -20,14 +40,34 @@ class JiraService < IssueTrackerService
 
   def reset_password
     # don't reset the password if a new one is provided
-    if api_url_changed? && !password_touched?
+    if url_changed? && !password_touched?
       self.password = nil
     end
   end
 
+  def options
+    url = URI.parse(self.url)
+    {
+      :username         => self.username,
+      :password         => self.password,
+      :site             => URI.join(url, '/').to_s,
+      :context_path     => url.path,
+      :auth_type        => :basic,
+      :read_timeout     => 120,
+      :use_ssl          => url.scheme == 'https'
+    }
+  end
+
+  def client
+    @client ||= ::JIRA::Client.new(options)
+  end
+
+  def jira_project
+    @jira_project ||= client.Project.find(project_key)
+  end
+
   def help
-    'Setting `project_url`, `issues_url` and `new_issue_url` will '\
-    'allow a user to easily navigate to the Jira issue tracker. See the '\
+    'See the ' \
     '[integration doc](http://doc.gitlab.com/ce/integration/external-issue-tracker.html) '\
     'for details.'
   end
@@ -53,12 +93,25 @@ class JiraService < IssueTrackerService
   end
 
   def fields
-    super.push(
-      { type: 'text', name: 'api_url', placeholder: 'https://jira.example.com/rest/api/2' },
+    [
+      { type: 'text', name: 'url', title: 'URL', placeholder: 'https://jira.example.com' },
+      { type: 'text', name: 'project_key', placeholder: 'PROJ' },
       { type: 'text', name: 'username', placeholder: '' },
       { type: 'password', name: 'password', placeholder: '' },
       { type: 'text', name: 'jira_issue_transition_id', placeholder: '2' }
-    )
+    ]
+  end
+
+  def project_url
+    "#{url}/issues/?jql=project=#{project_key}"
+  end
+
+  def issues_url
+    "#{url}/browse/:id"
+  end
+
+  def new_issue_url
+    "#{url}/secure/CreateIssue.jspa"
   end
 
   def execute(push, issue = nil)
@@ -72,7 +125,7 @@ class JiraService < IssueTrackerService
   end
 
   def create_cross_reference_note(mentioned, noteable, author)
-    issue_name = mentioned.id
+    issue_key = mentioned.id
     project = self.project
     noteable_name = noteable.class.name.underscore.downcase
     noteable_id = if noteable.is_a?(Commit)
@@ -94,53 +147,25 @@ class JiraService < IssueTrackerService
       },
       entity: {
         name: noteable_name.humanize.downcase,
-        url: entity_url,
-        title: noteable.title
+        url: entity_url
       }
     }
 
-    add_comment(data, issue_name)
+    add_comment(data, issue_key)
   end
 
   def test_settings
-    return unless api_url.present?
-    result = JiraService.get(
-      jira_api_test_url,
-      headers: {
-        'Content-Type' => 'application/json',
-        'Authorization' => "Basic #{auth}"
-      }
-    )
+    return unless api_utrl.present?
+    # Test settings by getting the project
+    jira_project
 
-    case result.code
-    when 201, 200
-      Rails.logger.info("#{self.class.name} SUCCESS #{result.code}: Successfully connected to #{api_url}.")
-      true
-    else
-      Rails.logger.info("#{self.class.name} ERROR #{result.code}: #{result.parsed_response}")
-      false
-    end
-  rescue Errno::ECONNREFUSED => e
-    Rails.logger.info "#{self.class.name} ERROR: #{e.message}. API URL: #{api_url}."
+  rescue Errno::ECONNREFUSED, JIRA::HTTPError => e
+    Rails.logger.info "#{self.class.name} Test ERROR: #{url} - #{e.message}"
     false
   end
 
   private
 
-  def build_api_url_from_project_url
-    server = URI(project_url)
-    default_ports = [["http", 80], ["https", 443]].include?([server.scheme, server.port])
-    server_url = "#{server.scheme}://#{server.host}"
-    server_url.concat(":#{server.port}") unless default_ports
-    "#{server_url}/rest/api/#{DEFAULT_API_VERSION}"
-  rescue
-    "" # looks like project URL was not valid
-  end
-
-  def set_api_url
-    self.api_url = build_api_url_from_project_url if self.api_url.blank?
-  end
-
   def set_jira_issue_transition_id
     self.jira_issue_transition_id ||= "2"
   end
@@ -149,7 +174,7 @@ class JiraService < IssueTrackerService
     commit_id = if entity.is_a?(Commit)
                   entity.id
                 elsif entity.is_a?(MergeRequest)
-                  entity.diff_head_sha
+                  entity.last_commit.id
                 end
     commit_url = build_entity_url(:commit, commit_id)
 
@@ -161,69 +186,47 @@ class JiraService < IssueTrackerService
   end
 
   def transition_issue(issue)
-    message = {
-      transition: {
-        id: jira_issue_transition_id
-      }
-    }
-    send_message(close_issue_url(issue.iid), message.to_json)
+    issue = client.Issue.find(issue.iid)
+    issue.transitions.build.save(transition: { id: jira_issue_transition_id })
   end
 
   def add_issue_solved_comment(issue, commit_id, commit_url)
-    comment = {
-      body: "Issue solved with [#{commit_id}|#{commit_url}]."
-    }
-
-    send_message(comment_url(issue.iid), comment.to_json)
+    comment = "Issue solved with [#{commit_id}|#{commit_url}]."
+    send_message(issue.iid, comment)
   end
 
-  def add_comment(data, issue_name)
-    url = comment_url(issue_name)
+  def add_comment(data, issue_key)
     user_name = data[:user][:name]
     user_url = data[:user][:url]
     entity_name = data[:entity][:name]
     entity_url = data[:entity][:url]
-    entity_title = data[:entity][:title]
     project_name = data[:project][:name]
 
-    message = {
-      body: %Q{[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title}'}
-    }
-
-    unless existing_comment?(issue_name, message[:body])
-      send_message(url, message.to_json)
-    end
-  end
+    message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]."
 
-  def auth
-    require 'base64'
-    Base64.urlsafe_encode64("#{self.username}:#{self.password}")
+    # unless existing_comment?(issue_name, message[:body])
+      send_message(issue_key, message)
+    # end
   end
 
-  def send_message(url, message)
+  def send_message(issue_key, message)
     return unless api_url.present?
-    result = JiraService.post(
-      url,
-      body: message,
-      headers: {
-        'Content-Type' => 'application/json',
-        'Authorization' => "Basic #{auth}"
-      }
-    )
-
-    message = case result.code
-              when 201, 200, 204
-                "#{self.class.name} SUCCESS #{result.code}: Successfully posted to #{url}."
-              when 401
-                "#{self.class.name} ERROR 401: Unauthorized. Check the #{self.username} credentials and JIRA access permissions and try again."
-              else
-                "#{self.class.name} ERROR #{result.code}: #{result.parsed_response}"
-              end
+    issue = client.Issue.find(issue_key)
+    issue.comments.build.save!(body: message)
+
+    # message = case result.code
+    #           when 201, 200, 204
+    #             "#{self.class.name} SUCCESS #{result.code}: Successfully posted to #{url}."
+    #           when 401
+    #             "#{self.class.name} ERROR 401: Unauthorized. Check the #{self.username} credentials and JIRA access permissions and try again."
+    #           else
+    #             "#{self.class.name} ERROR #{result.code}: #{result.parsed_response}"
+    #           end
 
     Rails.logger.info(message)
     message
   rescue URI::InvalidURIError, Errno::ECONNREFUSED => e
-    Rails.logger.info "#{self.class.name} ERROR: #{e.message}. Hostname: #{url}."
+    Rails.logger.info "#{self.class.name} Send message ERROR: #{url} - #{e.message}"
   end
 
   def existing_comment?(issue_name, new_comment)
@@ -267,16 +270,4 @@ class JiraService < IssueTrackerService
       )
     )
   end
-
-  def close_issue_url(issue_name)
-    "#{self.api_url}/issue/#{issue_name}/transitions"
-  end
-
-  def comment_url(issue_name)
-    "#{self.api_url}/issue/#{issue_name}/comment"
-  end
-
-  def jira_api_test_url
-    "#{self.api_url}/myself"
-  end
 end
diff --git a/db/migrate/20160122231710_migrate_jira_to_gem.rb b/db/migrate/20160122231710_migrate_jira_to_gem.rb
new file mode 100644
index 0000000000000000000000000000000000000000..972aea323d95729119ccdcd1c5fa86e649fe523c
--- /dev/null
+++ b/db/migrate/20160122231710_migrate_jira_to_gem.rb
@@ -0,0 +1,52 @@
+class MigrateJiraToGem < ActiveRecord::Migration
+  def change
+    reversible do |dir|
+      select_all("SELECT id, properties FROM services WHERE services.type IN ('JiraService')").each do |service|
+        id = service['id']
+        properties = JSON.parse(service['properties'])
+        properties_was = properties.clone
+
+        dir.up do
+          # Migrate `project_url` to `project_key`
+          # Ignore if `project_url` doesn't have jql project query with project key
+          if properties['project_url'].present?
+            jql = properties['project_url'].match('project=([A-Za-z]*)')
+            properties['project_key'] = jql.captures.first if jql
+          end
+
+          # Migrate `api_url` to `url`
+          if properties['api_url'].present?
+            url = properties['api_url'].match('(.*)\/rest\/api')
+            properties['url'] = url.captures.first if url
+          end
+
+          # Delete now unnecessary properties
+          properties.delete('api_url')
+          properties.delete('project_url')
+          properties.delete('new_issue_url')
+          properties.delete('issues_url')
+        end
+
+        dir.down do
+          # Rebuild old properties based on sane defaults
+          if properties['url'].present?
+            properties['api_url'] = "#{properties['url']}/rest/api/2"
+            properties['project_url'] =
+              "#{properties['url']}/issues/?jql=project=#{properties['project_key']}"
+            properties['issues_url'] = "#{properties['url']}/browse/:id"
+            properties['new_issue_url'] = "#{properties['url']}/secure/CreateIssue.jspa"
+          end
+
+          # Delete the new properties
+          properties.delete('url')
+          properties.delete('project_key')
+        end
+
+        # Update changes properties
+        if properties != properties_was
+          execute("UPDATE services SET properties = '#{quote_string(properties.to_json)}' WHERE id = #{id}")
+        end
+      end
+    end
+  end
+end
diff --git a/doc/integration/README.md b/doc/integration/README.md
index c2fd299db07a1240770433265f83ba42f2e73cca..0fc0be4ffceddc7eaea05a483a798d7b1df47e52 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -5,57 +5,36 @@ trackers and external authentication.
 
 See the documentation below for details on how to configure these services.
 
-- [Jira](../project_services/jira.md) Integrate with the JIRA issue tracker
+- [JIRA](jira.md) Integrate with the JIRA issue tracker
 - [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
 - [LDAP](ldap.md) Set up sign in via LDAP
-- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd and Azure
+- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth.
 - [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
 - [CAS](cas.md) Configure GitLab to sign in using CAS
+- [Slack](slack.md) Integrate with the Slack chat service
 - [OAuth2 provider](oauth_provider.md) OAuth2 application creation
 - [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
 - [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
-- [Akismet](akismet.md) Configure Akismet to stop spam
-- [Koding](../administration/integration/koding.md) Configure Koding to use IDE integration
 
 GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
 
-[jenkins]: http://docs.gitlab.com/ee/integration/jenkins.html
-
-
 ## Project services
 
 Integration with services such as Campfire, Flowdock, Gemnasium, HipChat,
 Pivotal Tracker, and Slack are available in the form of a [Project Service][].
+You can find these within GitLab in the Services page under Project Settings if
+you are at least a master on the project.
+Project Services are a bit like plugins in that they allow a lot of freedom in
+adding functionality to GitLab. For example there is also a service that can
+send an email every time someone pushes new commits.
 
-[Project Service]: ../project_services/project_services.md
-
-## SSL certificate errors
-
-When trying to integrate GitLab with services that are using self-signed certificates,
-it is very likely that SSL certificate errors will occur on different parts of the
-application, most likely Sidekiq. There are 2 approaches you can take to solve this:
-
-1. Add the root certificate to the trusted chain of the OS.
-1. If using Omnibus, you can add the certificate to GitLab's trusted certificates.
-
-**OS main trusted chain**
+Because GitLab is open source we can ship with the code and tests for all
+plugins. This allows the community to keep the plugins up to date so that they
+always work in newer GitLab versions.
 
-This [resource](http://kb.kerio.com/product/kerio-connect/server-configuration/ssl-certificates/adding-trusted-root-certificates-to-the-server-1605.html)
-has all the information you need to add a certificate to the main trusted chain.
+For an overview of what projects services are available without logging in,
+please see the [project_services directory][projects-code].
 
-This [answer](http://superuser.com/questions/437330/how-do-you-add-a-certificate-authority-ca-to-ubuntu)
-at SuperUser also has relevant information.
-
-**Omnibus Trusted Chain**
-
-It is enough to concatenate the certificate to the main trusted certificate:
-
-```bash
-cat jira.pem >> /opt/gitlab/embedded/ssl/certs/cacert.pem
-```
-
-After that restart GitLab with:
-
-```bash
-sudo gitlab-ctl restart
-```
+[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html
+[Project Service]: ../project_services/project_services.md
+[projects-code]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services
diff --git a/doc/integration/img/jira_service_page.png b/doc/integration/img/jira_service_page.png
new file mode 100644
index 0000000000000000000000000000000000000000..0cc160bebe2fda31497afd077b7cd0f5b0930dec
Binary files /dev/null and b/doc/integration/img/jira_service_page.png differ
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
index 78aa66341161d9f9966355477460ecee98becfd5..de373bd94ff370f738a65d0283c7900e4e3029ad 100644
--- a/doc/integration/jira.md
+++ b/doc/integration/jira.md
@@ -1,3 +1,146 @@
-# GitLab JIRA integration
+# GitLab Jira integration
 
-This document was moved under [project_services/jira](../project_services/jira.md).
+GitLab can be configured to interact with Jira. Configuration happens via
+username and password. Connecting to a Jira server via CAS is not possible.
+
+Each project can be configured to connect to a different Jira instance, see the
+[configuration](#configuration) section. If you have one Jira instance you can
+pre-fill the settings page with a default template. To configure the template
+see the [Services Templates][services-templates] document.
+
+Once the project is connected to Jira, you can reference and close the issues
+in Jira directly from GitLab.
+
+## Table of Contents
+
+* [Referencing Jira Issues from GitLab](#referencing-jira-issues)
+* [Closing Jira Issues from GitLab](#closing-jira-issues)
+* [Configuration](#configuration)
+
+### Referencing Jira Issues
+
+When GitLab project has Jira issue tracker configured and enabled, mentioning
+Jira issue in GitLab will automatically add a comment in Jira issue with the
+link back to GitLab. This means that in comments in merge requests and commits
+referencing an issue, eg. `PROJECT-7`, will add a comment in Jira issue in the
+format:
+
+```
+ USER mentioned this issue in LINK_TO_THE_MENTION
+```
+
+* `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
+* `LINK_TO_THE_MENTION` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
+Can be commit or merge request.
+
+![example of mentioning or closing the Jira issue](img/jira_issue_reference.png)
+
+---
+
+### Closing Jira Issues
+
+Jira issues can be closed directly from GitLab by using trigger words, eg.
+`Resolves PROJECT-1`, `Closes PROJECT-1` or `Fixes PROJECT-1`, in commits and
+merge requests. When a commit which contains the trigger word in the commit
+message is pushed, GitLab will add a comment in the mentioned Jira issue.
+
+For example, for project named `PROJECT` in Jira, we implemented a new feature
+and created a merge request in GitLab.
+
+This feature was requested in Jira issue `PROJECT-7`. Merge request in GitLab
+contains the improvement and in merge request description we say that this
+merge request `Closes PROJECT-7` issue.
+
+Once this merge request is merged, the Jira issue will be automatically closed
+with a link to the commit that resolved the issue.
+
+![A Git commit that causes the Jira issue to be closed](img/jira_merge_request_close.png)
+
+---
+
+![The GitLab integration user leaves a comment on Jira](img/jira_service_close_issue.png)
+
+---
+
+## Configuration
+
+### Configuring JIRA
+
+We need to create a user in JIRA which will have access to all projects that
+need to integrate with GitLab. Login to your JIRA instance as admin and under
+Administration go to User Management and create a new user.
+
+As an example, we'll create a user named `gitlab` and add it to `jira-developers`
+group.
+
+**It is important that the user `gitlab` has write-access to projects in JIRA**
+
+### Configuring GitLab
+
+JIRA configuration in GitLab is done via a project's **Services**.
+
+#### GitLab 7.8 and up with JIRA v6.x
+
+See next section.
+
+#### GitLab 7.8 and up
+
+_The currently supported JIRA versions are v6.x and v7.x._
+
+To enable JIRA integration in a project, navigate to the project's
+**Settings > Services > JIRA**.
+
+Fill in the required details on the page as described in the table below.
+
+| Field | Description |
+| ----- | ----------- |
+| `URL` | The base URL to the JIRA project which is being linked to this GitLab project. Ex. https://jira.example.com |
+| `Project key` | The short, all capital letter identifier for your JIRA project. |
+| `Username` | The username of the user created in [configuring JIRA step](#configuring-jira). |
+| `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
+| `Jira issue transition` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)).  By default, this ID is `2` (in the example image, this is `2` as well) |
+
+After saving the configuration, your GitLab project will be able to interact
+with the linked JIRA project.
+
+![Jira service page](img/jira_service_page.png)
+
+---
+
+#### GitLab 6.x-7.7 with JIRA v6.x
+
+_**Note:** GitLab versions 7.8 and up contain various integration improvements.
+We strongly recommend upgrading._
+
+In `gitlab.yml` enable the JIRA issue tracker section by
+[uncommenting these lines][jira-gitlab-yml]. This will make sure that all
+issues within GitLab are pointing to the JIRA issue tracker.
+
+After you set this, you will be able to close issues in JIRA by a commit in
+GitLab.
+
+Go to your project's **Settings** page and fill in the project name for the
+JIRA project:
+
+![Set the JIRA project name in GitLab to 'NEW'](img/jira_project_name.png)
+
+---
+
+You can also enable the JIRA service that will allow you to interact with JIRA
+issues. Go to the **Settings > Services > JIRA** and:
+
+1. Tick the active check box to enable the service
+1. Supply the URL to JIRA server, for example http://jira.example.com
+1. Supply the username of a user we created under `Configuring JIRA` section,
+   for example `gitlab`
+1. Supply the password of the user
+1. Optional: supply the JIRA API version, default is version `2`
+1. Optional: supply the JIRA issue transition ID (issue transition to closed).
+   This is dependent on JIRA settings, default is `2`
+1. Hit save
+
+
+![Jira services page](img/jira_service.png)
+
+[services-templates]: ../project_services/services_templates.md
+[jira-gitlab-yml]: https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115